【问题标题】:How to send a request to upload image file to LINE server with multipart/form-data for posting image to LINE Notify?如何使用 multipart/form-data 发送将图像文件上传到 LINE 服务器的请求,以便将图像发布到 LINE Notify?
【发布时间】:2019-06-19 04:27:42
【问题描述】:

我正在尝试使用 LINE Notify 从本地发布图像,但收到错误请求,如何在 vb.net 中使用带有 multipart/form-data 内容类型的 HttpWebRequest 发送正确的请求?

我已经尝试过 cURL,它正在工作:

curl -i -X POST https://notify-api.line.me/api/notify -H "Authorization: Bearer <TOKEN>" -F "message=test" -F "imageFile=@C:\PATH\to\file.jpg"

这是我在 vb.net 中所做的:

Imports System.IO
Imports System.Net
Imports System.Security.Cryptography
Imports System.Text

Public Class Form1
    Private Sub btnPic_Click(sender As Object, e As EventArgs) Handles btnPic.Click
        OpenFileDialog1.Filter = "Image File (*.jpg)|*.jpg;*.JPG |Image File (*.png)|*.png;*.PNG"
        OpenFileDialog1.FileName = ""

        If OpenFileDialog1.ShowDialog() <> DialogResult.Cancel Then
            txtPic.Text = OpenFileDialog1.FileName
        End If
    End Sub

    Private Sub btnSend_Click(sender As Object, e As EventArgs) Handles btnSend.Click
        ' [references]
        'https://notify-bot.line.me/doc/en/
        'http://white5168.blogspot.com/2017/01/line-notify-6-line-notify.html
        'https://aprico-media.com/posts/1824
        'https://stackoverflow.com/questions/3890754/using-httpwebrequest-to-post-data-upload-image-using-multipart-form-data

        Dim md5Hash As MD5 = MD5.Create()
        'Boundary string
        Dim strBound As String = "---------------------" & GetMd5Hash(md5Hash, CInt(Int((654321 * Rnd()) + 1))).Substring(0, 10)

        Try
            Dim request = DirectCast(WebRequest.Create("https://notify-api.line.me/api/notify"), HttpWebRequest)
            Dim postData As String = "--" & strBound & vbCrLf
            postData += "Content-Disposition: form-data; name=""message""" & vbCrLf & vbCrLf
            postData += txtText.Text & vbCrLf
            ' [Not working] sticker part
            'postData += "--" & strBound & vbCrLf
            'postData += "Content-Disposition: form-data; name=""stickerPackageId""" & vbCrLf & vbCrLf
            'postData += 1 & vbCrLf
            'postData += "--" & strBound & vbCrLf
            'postData += "Content-Disposition: form-data; name=""stickerId""" & vbCrLf & vbCrLf
            'postData += 2 & vbCrLf

            If txtPic.Text <> "" Then
                Dim ext As String = Path.GetExtension(txtPic.Text)
                If ext = ".jpg" Then
                    ext = "jpeg"
                ElseIf ext = ".png" Then
                    ext = "png"
                Else
                    MessageBox.Show("Sorry! LINE Notify supports only jpeg/png image file.")
                    btnPic.PerformClick()
                    Return
                End If
                ' [Not working] image part
                postData += "--" & strBound & vbCrLf
                postData += "Content-Disposition: form-data; name=""imageFile""; filename=""" & Path.GetFileName(txtPic.Text) & """" & vbCrLf
                postData += "Content-Type: image/" & ext & vbCrLf & vbCrLf
                postData += Convert.ToBase64String(File.ReadAllBytes(txtPic.Text)) & vbCrLf
            End If
            postData += vbCrLf & "--" & strBound & "--"
            Dim data = Encoding.UTF8.GetBytes(postData)

            request.Method = "POST"
            request.ContentType = "multipart/form-data; boundary=" & strBound
            request.ContentLength = data.Length
            request.Headers.Add("Authorization", "Bearer <TOKEN>")

            Using stream = request.GetRequestStream()
                stream.Write(data, 0, data.Length)
            End Using

            Dim response = DirectCast(request.GetResponse(), HttpWebResponse)
            Dim responseString = New StreamReader(response.GetResponseStream()).ReadToEnd()
        Catch ex As Exception
            MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1)
        End Try
    End Sub

    Shared Function GetMd5Hash(ByVal md5Hash As MD5, ByVal Input As String) As String

        ' Convert the input string to a byte array and compute the hash.
        Dim data As Byte() = md5Hash.ComputeHash(Encoding.UTF8.GetBytes(Input))

        ' Create a new Stringbuilder to collect the bytes
        ' and create a string.
        Dim sBuilder As New StringBuilder()

        ' Loop through each byte of the hashed data 
        ' and format each one as a hexadecimal string.
        Dim i As Integer
        For i = 0 To data.Length - 1
            sBuilder.Append(data(i).ToString("x2"))
        Next i

        ' Return the hexadecimal string.
        Return sBuilder.ToString()

    End Function 'GetMd5Hash

End Class

只发送“消息”没有问题,只要将其他部分即“imageFile”,“stickerPackageId”,“stickerId”,...结合起来,结果就会出现错误(400)。但是,如果将字段名称更改为小写字母,即“stickerpackageid”,“stickerid”vb 将不会捕获任何异常,而只是将“消息”发送给 Notify。所以,我认为http请求字符串中应该有错误的部分,还是需要将每个字段转换为二进制数组?如果是这样,如何获得正确的结果?

【问题讨论】:

    标签: vb.net multipartform-data line-api


    【解决方案1】:

    post multipart/form-data for line notify 需要自己生成输出字节数组列表 LINE Notify API 参考https://notify-bot.line.me/doc/en/

    上传文件类

    public class FormFile
    {
        public string Name { get; set; }
    
        public string ContentType { get; set; }
    
        public string FilePath { get; set; }
    
        public byte[] bytes { get; set; }
    }
    

    测试数据

    private void multipartTest(string access_token)
    {
        Dictionary<string, object> d = new Dictionary<string, object>()
        {
            // message , imageFile ... name is provided by LINE API
            { "message", @"message..." },
            { "imageFile", new FormFile(){ Name = "notify.jpg", ContentType = "image/jpeg", FilePath="notify.jpg" }
            }
        };
    
        string boundary = "Boundary";
        List<byte[]> output = genMultPart(d, boundary);
        lineNotifyMultipart(access_token, boundary, output);
    }
    

    C#代码

    private void lineNotifyMultipart(string access_token, string boundary, List<byte[]> output)
    {
        try
        {
            #region POST multipart/form-data
            StringBuilder sb = new StringBuilder();
            HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(lineNotifyURL);
            request.Method = "POST";
            request.ContentType = "multipart/form-data; boundary=Boundary";
            request.Timeout = 30000;
    
            // header
            sb.Clear();
            sb.Append("Bearer ");
            sb.Append(access_token);
            request.Headers.Add("Authorization", sb.ToString());
            // note: multipart/form-data boundary must exist in headers ContentType
            sb.Clear();
            sb.Append("multipart/form-data; boundary=");
            sb.Append(boundary);
            request.ContentType = sb.ToString();
    
            // write Post Body Message
            BinaryWriter bw = new BinaryWriter(request.GetRequestStream());
            foreach(byte[] bytes in output)
                bw.Write(bytes);
    
            #endregion
    
            getResponse(request);
        }
        catch (Exception ex)
        {
            #region Exception
            StringBuilder sbEx = new StringBuilder();
            sbEx.Append(ex.GetType());
            sbEx.AppendLine();
            sbEx.AppendLine(ex.Message);
            sbEx.AppendLine(ex.StackTrace);
            if (ex.InnerException != null)
                sbEx.AppendLine(ex.InnerException.Message);
            myException ex2 = new myException(sbEx.ToString());
            //message(ex2.Message);
            #endregion
        }
    }
    
    private void getResponse(HttpWebRequest request)
    {
        StringBuilder sb = new StringBuilder();
        string result = string.Empty;
        StreamReader sr = null;
        try
        {
            #region Get Response
            if (request == null)
                return;
            // HttpWebRequest GetResponse() if error happened will trigger WebException
            using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
            {
                sb.AppendLine();
                foreach (var x in response.Headers)
                {
                    sb.Append(x);
                    sb.Append(" : ");
                    sb.Append(response.Headers[x.ToString()]);
                    if (x.ToString() == "X-RateLimit-Reset")
                    {
                        sb.Append(" ( ");
                        sb.Append(CheckFormat.ToEpcohDateTimeUTC(long.Parse(response.Headers[x.ToString()])));
                        sb.Append(" )");
                    }
                    sb.AppendLine();
                }
                using (sr = new StreamReader(response.GetResponseStream()))
                {
                    result = sr.ReadToEnd();
                    sb.Append(result);
                }
            }
    
            //message(sb.ToString());
    
            #endregion
        }
        catch (WebException ex)
        {
            #region WebException handle
            // WebException Response
            using (HttpWebResponse response = (HttpWebResponse)ex.Response)
            {
                sb.AppendLine("Error");
                foreach (var x in response.Headers)
                {
                    sb.Append(x);
                    sb.Append(" : ");
                    sb.Append(response.Headers[x.ToString()]);                    
                    sb.AppendLine();
                }
                using (sr = new StreamReader(response.GetResponseStream()))
                {
                    result = sr.ReadToEnd();
                    sb.Append(result);
                }
    
                //message(sb.ToString());
            }
    
            #endregion
        }
    }
    
    public List<byte[]> genMultPart(Dictionary<string, object> parameters, string boundary)
    {
        StringBuilder sb = new StringBuilder();
        sb.Clear();
        sb.Append("\r\n--");
        sb.Append(boundary);
        sb.Append("\r\n");
        string beginBoundary = sb.ToString();
        sb.Clear();
        sb.Append("\r\n--");
        sb.Append(boundary);
        sb.Append("--\r\n");
        string endBoundary = sb.ToString();
        sb.Clear();
        sb.Append("Content-Type: multipart/form-data; boundary=");
        sb.Append(boundary);
        sb.Append("\r\n");
        List<byte[]> byteList = new List<byte[]>();
        byteList.Add(System.Text.Encoding.UTF8.GetBytes(sb.ToString()));
    
        foreach (KeyValuePair<string, object> pair in parameters)
        {
            if (pair.Value is FormFile)
            {
                byteList.Add(System.Text.Encoding.ASCII.GetBytes(beginBoundary));
                FormFile form = pair.Value as FormFile;
    
                sb.Clear();
                sb.Append("Content-Disposition: form-data; name=\"");
                sb.Append(pair.Key);
                sb.Append("\"; filename=\"");
                sb.Append(form.Name);
                sb.Append("\"\r\nContent-Type: ");
                sb.Append(form.ContentType);
                sb.Append("\r\n\r\n");
                byte[] bytes = System.Text.Encoding.UTF8.GetBytes(sb.ToString());
                byteList.Add(bytes);
                if (form.bytes == null && !string.IsNullOrEmpty(form.FilePath))
                {    
                    FileStream fs = new FileStream(form.FilePath, FileMode.Open, FileAccess.Read);
                    MemoryStream ms = new MemoryStream();
                    fs.CopyTo(ms);
                    byteList.Add(ms.ToArray());
                }
                else
                    byteList.Add(form.bytes);
            }
            else
            {
                byteList.Add(System.Text.Encoding.ASCII.GetBytes(beginBoundary));
                sb.Clear();
                sb.Append("Content-Disposition: form-data; name=\"");
                sb.Append(pair.Key);
                sb.Append("\"");
                sb.Append("\r\n\r\n");
                sb.Append(pair.Value);
                string data = sb.ToString();
                byte[] bytes = System.Text.Encoding.UTF8.GetBytes(data);
                byteList.Add(bytes);
            }
        }
    
        byteList.Add(System.Text.Encoding.ASCII.GetBytes(endBoundary));
        return byteList;
    }    
    

    【讨论】:

    • 是的,关键是使用“字节数组”列表,而不是二进制数组,现在可以直接从.NET 发送imageFile。非常感谢。
    猜你喜欢
    • 1970-01-01
    • 2015-06-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-14
    • 2019-01-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多