【问题标题】:POST a Multi-Part Form to Bamboo API将多部分表单发布到 Bamboo API
【发布时间】:2017-09-20 09:18:11
【问题描述】:

我在通过 VB.NET 控制台应用程序向 BambooHR API 提交多部分表单时遇到了很多困难。我已经发布了我当前的代码以及下面文档中的示例请求,当我运行此代码时,我得到 (400) Bad Request。我知道代码很乱,但我一直在努力让它工作。

我能够使用他们的示例代码使 GET 请求工作,但他们没有任何代码来执行此特定 API 调用(上传员工文件)。

任何帮助将不胜感激。

这是我的代码:

Sub Main()

    upload(id, "https://api.bamboohr.com/api/gateway.php/company")

    Console.WriteLine()
    Console.WriteLine("Press ENTER to quit")
    Console.ReadLine()
End Sub

Function upload(ByVal employeeId As Integer, ByVal baseUrl As String)

    ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 Or SecurityProtocolType.Ssl3

    Dim boundary = "----BambooHR-MultiPart-Mime-Boundary----"
    Dim url = String.Format("{0}/v1/employees/{1}/files/", baseUrl, employeeId)

    Dim request As HttpWebRequest = WebRequest.Create(url)
    request.KeepAlive = True
    request.Method = "POST"
    request.ContentType = "multipart/form-data; boundary=" + boundary

    'Authorization is just the api key and a random string, in this case is x
    '
    Dim authInfo As String = api_key + ":" + "x"
    authInfo = Convert.ToBase64String(Encoding.Default.GetBytes(authInfo))
    request.Headers("Authorization") = "Basic " + authInfo


    Dim memStream As New MemoryStream()

    WriteMPF(memStream)

    request.ContentLength = memStream.Length

    Using requestStream = request.GetRequestStream()
        memStream.Position = 0
        Dim tempBuffer As Byte() = New Byte(memStream.Length - 1) {}
        memStream.Read(tempBuffer, 0, tempBuffer.Length)
        memStream.Close()
        requestStream.Write(tempBuffer, 0, tempBuffer.Length)
    End Using

    Dim webresponse As HttpWebResponse = request.GetResponse()
    Return webresponse

End Function

Private Sub WriteMPF(s As Stream)

    WriteToStream(s, "POST /api/gateway.php/company/v1/employees/id/files/ HTTP/1.0")
    WriteToStream(s, vbCr & vbLf)
    WriteToStream(s, "Host: api.bamboohr.com")
    WriteToStream(s, vbCr & vbLf)
    WriteToStream(s, "Content-Type: multipart/form-data; boundary=----BambooHR-MultiPart-Mime-Boundary----")
    WriteToStream(s, vbCr & vbLf)
    WriteToStream(s, "Content-Length: 520")
    WriteToStream(s, vbCr & vbLf)
    WriteToStream(s, vbCr & vbLf)

    WriteToStream(s, "------BambooHR-MultiPart-Mime-Boundary----")
    WriteToStream(s, vbCr & vbLf)
    WriteToStream(s, "Content-Disposition: form-data; name=""category""")
    WriteToStream(s, vbCr & vbLf)
    WriteToStream(s, vbCr & vbLf)
    WriteToStream(s, "14")
    WriteToStream(s, vbCr & vbLf)

    WriteToStream(s, "------BambooHR-MultiPart-Mime-Boundary----")
    WriteToStream(s, vbCr & vbLf)
    WriteToStream(s, "Content-Disposition: form-data; name=""fileName""")
    WriteToStream(s, vbCr & vbLf)
    WriteToStream(s, vbCr & vbLf)
    WriteToStream(s, "test.txt")
    WriteToStream(s, vbCr & vbLf)

    WriteToStream(s, "------BambooHR-MultiPart-Mime-Boundary----")
    WriteToStream(s, vbCr & vbLf)
    WriteToStream(s, "Content-Disposition: form-data; name=""share""")
    WriteToStream(s, vbCr & vbLf)
    WriteToStream(s, vbCr & vbLf)
    WriteToStream(s, "no")
    WriteToStream(s, vbCr & vbLf)

    WriteToStream(s, "------BambooHR-MultiPart-Mime-Boundary----")
    WriteToStream(s, vbCr & vbLf)
    WriteToStream(s, "Content-Disposition: form-data; name=""file""; filename = ""test.txt""")
    WriteToStream(s, vbCr & vbLf)
    WriteToStream(s, "Content-Type: text/plain")
    WriteToStream(s, vbCr & vbLf)
    WriteToStream(s, vbCr & vbLf)
    WriteToStream(s, "this is a test!")
    WriteToStream(s, vbCr & vbLf)

    WriteToStream(s, vbCr & vbLf)
    WriteToStream(s, "------BambooHR-MultiPart-Mime-Boundary------")
    WriteToStream(s, vbCr & vbLf)
End Sub

Private Sub WriteToStream(s As Stream, txt As String)
    Dim bytes As Byte() = Encoding.UTF8.GetBytes(txt)
    s.Write(bytes, 0, bytes.Length)
End Sub

这是来自文档的示例请求:(链接:https://www.bamboohr.com/api/documentation/employees.php 向下滚动到“上传员工文件”)

POST /api/gateway.php/sample/v1/employees/1/files/HTTP/1.0 主办方:api.bamboohr.com 内容类型:multipart/form-data;边界=----BambooHR-MultiPart-Mime-Boundary---- 内容长度:520

-----BambooHR-MultiPart-Mime-Boundary---- 内容处置:表单数据;名称="类别"

112 ------BambooHR-MultiPart-Mime-Boundary---- 内容处置:表单数据; name="文件名"

自述文件.txt ------BambooHR-MultiPart-Mime-Boundary---- 内容处置:表单数据;名称="分享"

是的 ------BambooHR-MultiPart-Mime-Boundary---- 内容处置:表单数据;名称=“文件”;文件名="readme.txt" 内容类型:text/plain

这是一个示例文本文件。

-----BambooHR-MultiPart-Mime-Boundary-----

【问题讨论】:

  • (400) 他们的文档中没有提到错误请求。也许你应该联系他们并询问这意味着什么。
  • 这里确实这么说:bamboohr.com/api/documentation
  • 你不应该自己做 multipart/form-data (RFC 1867) 的构造,有很多事情需要处理。现在使用 .NET 4.5 应该很容易。检查这个:stackoverflow.com/questions/16416601/…

标签: vb.net post .net-4.5 multipartform-data


【解决方案1】:

使用他们 GitHub 上的 php 示例并将其复制到 VB.NET。这有点混乱,但它有效。以下是相关代码:

Public Function sendRequestMPF(ByVal req As BambooHTTPRequest, ByVal fileLocation As String) As BambooHTTPResponse
        ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 Or SecurityProtocolType.Ssl3

        Dim request As HttpWebRequest = WebRequest.Create(req.url)
        request.Method = req.method
        request.Host = "api.bamboohr.com"

        Dim boundary = "----BambooHR-MultiPart-Mime-Boundary----"

        Try
            request.ContentType = "multipart/form-data; boundary=" + boundary
            request.ContentLength = req.contents.Length
        Catch ex As Exception

        End Try

        Dim iCount As Integer = req.headers.Count
        Dim key As String
        Dim keyvalue As String

        Dim i As Integer
        For i = 0 To iCount - 1
            key = req.headers.Keys(i)
            keyvalue = req.headers(i)
            request.Headers.Add(key, keyvalue)
        Next

        Dim enc As System.Text.UTF8Encoding = New System.Text.UTF8Encoding()
        Dim bytes() As Byte = {}
        Dim pdfBytes() As Byte = {}
        Dim lBytes() As Byte = {}

        Dim fBytes() As Byte = {}
        Dim s As New MemoryStream()

        If (req.contents.Length > 0) Then
            bytes = enc.GetBytes(req.contents)
            s.Write(bytes, 0, bytes.Length)

            pdfBytes = File.ReadAllBytes(fileLocation)
            s.Write(pdfBytes, 0, pdfBytes.Length)

            Dim postHeader = vbCrLf + vbCrLf + "--" + boundary + "--" + vbCrLf
            Dim postHeaderBytes() As Byte = enc.GetBytes(postHeader)
            lBytes = enc.GetBytes(postHeader)
            s.Write(postHeaderBytes, 0, postHeaderBytes.Length)

            fBytes = s.ToArray()
            request.ContentLength = fBytes.Length
        End If

        request.AllowAutoRedirect = False

        If Not basicAuthUsername.Equals("") Then
            Dim authInfo As String = basicAuthUsername + ":" + basicAuthPassword
            authInfo = Convert.ToBase64String(Encoding.Default.GetBytes(authInfo))
            request.Headers("Authorization") = "Basic " + authInfo
        End If

        If req.contents.Length > 0 Then
            Dim outBound As Stream = request.GetRequestStream()
            outBound.Write(fBytes, 0, fBytes.Length)
        End If

        Dim resp As BambooHTTPResponse
        Try
            Dim webresponse As HttpWebResponse = request.GetResponse()
            resp = New BambooHTTPResponse(webresponse)
            resp.responseCode = webresponse.StatusCode
            resp.headers = webresponse.Headers
        Catch e As WebException
            Console.WriteLine(e.Message)
            If (e.Status = WebExceptionStatus.ProtocolError) Then
                resp = New BambooHTTPResponse(DirectCast(e.Response, HttpWebResponse).StatusCode)
            Else
                resp = New BambooHTTPResponse(0)
            End If
        End Try

        Return resp
    End Function

Public Function buildMultiPart(ByVal params As NameValueCollection, ByVal boundary As String, ByVal contentType As String, ByVal name As String, ByVal fileName As String)
        Dim data = ""

        For Each key In params.AllKeys
            data += "--" + boundary + vbCrLf
            data += "Content-Disposition: form-data; name=""" + key + """"
            data += vbCrLf + vbCrLf
            data += params(key) + vbCrLf
        Next

        data += "--" + boundary + vbCr + vbLf
        data += "Content-Disposition: form-data; name=""" + name + """;" + " filename=""" + fileName + """" + vbCrLf
        data += "Content-Type: " + contentType + vbCrLf
        data += vbCrLf
        'data += fileData + vbCrLf + vbCrLf
        'data += "--" + boundary + "--" + vbCrLf

        Return data
    End Function

    Public Function uploadEmployeeFile(ByVal employeeId As Integer, ByVal fileName As String, ByVal fileLocation As String)
        Dim request As New BambooHTTPRequest()
        request.url = String.Format("{0}/v1/employees/{1}/files/", Me.baseUrl, employeeId)
        request.method = "POST"

        Dim boundary = "----BambooHR-MultiPart-Mime-Boundary----"

        Dim params = New NameValueCollection
        params.Add("category", "13")
        params.Add("fileName", fileName)
        params.Add("share", "no")

        request.contents = buildMultiPart(params, boundary, "application/pdf", "file", fileName)

        Return http.sendRequestMPF(request, fileLocation)
    End Function

所需的其余代码可以在他们的 GitHub 上找到 https://github.com/BambooHR

【讨论】:

    【解决方案2】:

    我怀疑至少您的Content-Length: 520 会出错。该内容长度仅适用于他们的示例。

    无论如何,我已经很久很久没有编写过 VB.Net,但是通过快速测试,此代码的修改版本适用于我的一个 REST 服务,因此它应该适用于您的情况,也许有一些微调。

    我的测试控制台项目使用 .Net 4.6.1,但可能会与一些早期的 .Net 框架一起运行。

    Imports System.IO
    Imports System.Net.Http
    
    Module Module1
    
        Sub Main()
            Call UploadFileToWebsite(14, "no", "D:\Temp\file.pdf")
            Console.WriteLine("Please wait for a response from the server and then press a key to continue.")
            Console.ReadKey()
        End Sub
    
        Public Sub UploadFileToWebsite(category As Integer, share As String, file As String)
            Dim message = New HttpRequestMessage()
            Dim content = New MultipartFormDataContent()
    
            content.Add(New StringContent(category.ToString()), "category")
            content.Add(New StringContent(share), "share")
    
            Dim filestream = New FileStream(file, FileMode.Open)
            Dim fileName = System.IO.Path.GetFileName(file)
    
            content.Add(New StreamContent(filestream), "file", fileName)
    
            message.Method = HttpMethod.Post
            message.Content = content
            message.RequestUri = New Uri("https://api.bamboohr.com/api/gateway.php/company")
    
            Dim client = New HttpClient()
            client.SendAsync(message).ContinueWith(
                Sub(task)
                    'do something with response
                    If task.Result.IsSuccessStatusCode Then
                        Console.WriteLine("Uploaded OK.")
                    Else
                        Console.WriteLine("Upload Failed.")
                    End If
                End Sub)
        End Sub
    End Module
    

    在不相关的注释上,您也可以使用vbCrLf 代替vbCr & vbLf

    【讨论】:

    • 你需要调整我使用的Uri
    • 我复制了您的代码并更改了文件路径和 Uri,并在该行得到“在 mscorlib.dll 中发生类型为 'System.AggregateException' 但未在用户代码中处理的异常”在运行时以“If task.Result ...”开头。我想知道这是否与授权有关?我必须添加 API 密钥作为正常请求的授权(不是多部分表单)
    • 是的,您还需要添加正常授权。多部分表单没有什么特别之处,它只是关于如何将文件包含在标准表单数据中的约定,以便可以在接收端将所有内容分开。我没有站点授权,所以我无法调查错误,但我建议你在If task.. 行设置一个断点并检查异常
    • 查看 BambooHR 网站,看起来 URI 请求可以是 https://{API Key}:x@api.bamboohr.com/api/gateway.php/{subdomain}/v1/employees/... 的形式。
    • 嗨,K,我有点太忙了,没时间做这个。我奖励你只是因为你一直在努力提供帮助,当我有机会再次工作时,我会更新你,谢谢!
    猜你喜欢
    • 1970-01-01
    • 2014-11-21
    • 2017-06-06
    • 2023-03-30
    • 2014-01-08
    • 2018-01-27
    • 2019-07-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多