如何使用erlang HTTP方法发送MIME(多部分媒体封装)内容类型消息?

问题描述 投票:0回答:1

我目前正在 Erlang 中开发一个模块,该模块发送一个 HTTP POST 请求,其中包含包含 JSON 和二进制数据的多部分/相关消息。但是,我在正确连接 JSON 消息和二进制数据时遇到问题。

-module(MIME_post).
-export([send_post_request/0, get_modify_req/0]).

send_post_request() ->
    % ... (existing code)

    Url = "http://localhost:8666",

    JsonData = #{<<"some_val">> => <<"imsi-460886666660006">>},
    JsonBody = jsx:encode(JsonData),

    Headers = [{"Content-Type", "multipart/related;boundary=-21ba7406cff5fa6c192d6b78fe58e16c5fd0cde4111e10f11c2bf55b45a5"}],

    MimeBinary = <<"2e0a00d1">>,
    Request = {Url, Headers, "multipart/related", JsonBody}, 

%here i want to include that MIME binary the Json body should get recognised as json and MIME binary should get recognized as another content type 

    case httpc:request(post, Request, [stream], []) of
        {ok, {{_, 201, _}, _Headers, ResponseBody}} ->
            io:format("HTTP Response Header:~p~n", [Headers]),
            io:format("HTTP Response: ~s~n", [ResponseBody]),
            {ok, ResponseBody};
        {error, Reason} ->
            io:format("HTTP Request failed with reason: ~p~n", [Reason]),
            {error, Reason}
    end.
http erlang multipart cowboy mimemultipart
1个回答
1
投票
           request(Method, Request, HttpOptions, Options)  
case httpc:request(post,   Request, [stream],    []     ) of  

stream
不是可用的 HttpOptions 之一:

HttpOption = 
    {timeout, timeout()} |
    {connect_timeout, timeout()} |
    {ssl, [ssl:tls_option()]} |
    {autoredirect, boolean()} |
    {proxy_auth, {string(), string()}} |
    {version, HttpVersion} |
    {relaxed, boolean()}

stream
Options
的第四个参数允许的
httpc:request/4
之一,并且它应该与一个值一起位于 2 元组中。

在您的

Content-Type
标题中,您可能想要更改:

-21ba....

至:

21ba...

另外,补充一下:

;type=\"application/json\" 

到最后。 必填

您可能还需要指定一个

Content-Length
标头来指定正文中的字节数。

这就是您希望请求正文的样子:

--21ba7406cff5fa6c192d6b78fe58e16c5fd0cde4111e10f11c2bf55b45a5
Content-Type: application/json

{"some_key":"imsi-460886666660006"}
--21ba7406cff5fa6c192d6b78fe58e16c5fd0cde4111e10f11c2bf55b45a5
Content-Type: application/octet-stream

2e0a00d1
--21ba7406cff5fa6c192d6b78fe58e16c5fd0cde4111e10f11c2bf55b45a5--

注意最后一个边界上的尾随“--”。您必须手工组装所有这些。

请求正文中出现的实际边界由以下部分组成:

  1. 初始回车+换行,称为

    CRLF
    ,由字符“组成” “(这是两个八位字节)。省略正文中第一个边界的初始 CRLF。

  2. 两个字符“--”。

  3. Content-Type 标头中指定的边界。

  4. 终止 CRLF,或者如果它是最后一个边界,则终止“--”。

请参阅 RFC 2406,第 5.1.1 节RFC 822,第 3.2 节

组装主体的最简单方法可能是使用

iolist
,它是一个包含整数 0..255 或二进制文件或 ascii 字符串(或包含任何这些类型的子列表)的列表。在这里,iolist 的有用特征是 erlang 自动将所有内容连接在一起。下面的代码使用字符串列表来利用连接效果:

send_post_request() ->
    % ... (existing code)

    Url = "http://localhost:8080/print_request",

    JsonData = #{<<"some_key">> => <<"imsi-460886666660006">>},
    JsonBody = jsx:encode(JsonData),

    MimeBinary = <<"2e0a00d1">>,

    Boundary = "21ba7406cff5fa6c192d6b78fe58e16c5fd0cde4111e10f11c2bf55b45a5",
    CRLF = "\r\n",

    ContentTypeHeader = [   % this is an iolist() 
        "multipart/related; ",
        "boundary=", Boundary, 
        "; type=\"application/json\""
    ], 
    
    RequestBody = [  % this is an iolist()
        "--", Boundary, CRLF,
        "Content-Type: application/json", CRLF, CRLF,
        JsonBody,
        CRLF, "--", Boundary, CRLF,
        "Content-Type: application/octet-stream", CRLF, CRLF,
        MimeBinary, 
        CRLF, "--", Boundary, "--"
    ],

    BodyBinary = iolist_to_binary(RequestBody),
    ContentLength = byte_size(BodyBinary),

    AdditionalHeaders = [
        {
            "Content-Length", 
            integer_to_list(ContentLength)
        } 
    ],

    Request = {Url, AdditionalHeaders, ContentTypeHeader, BodyBinary}, 

    Response = httpc:request(post, Request, [], []),
    io:format("Response:~n~p~n", [Response]).
    

使用此 Express 服务器(https://www.derpturkey.com/node-multipart-form-data-explained/):

let express = require('express');
let app     = express();
const port = 8080;

app.post('/print_request', (req, res) => {

  // output the headers
  console.log(req.headers);

 // capture the encoded form data
 req.on('data', (data) => {
   console.log(data.toString());
});

  // send a response when finished reading
  // the encoded form data
  req.on('end', () => {
    res.send('ok');
  });
});

// start server on port 8080
app.listen(port, () => {
  console.log(`Listening on port ${port}`)
});

...这是我在服务器窗口中看到的输出:

Listening on port 8080
{
  'content-type': 'multipart/related; boundary=21ba7406cff5fa6c192d6b78fe58e16c5fd0cde4111e10f11c2bf55b45a5; type="application/json"',
  'content-length': '315',
  te: '',
  host: 'localhost:8080',
  connection: 'keep-alive'
}
--21ba7406cff5fa6c192d6b78fe58e16c5fd0cde4111e10f11c2bf55b45a5
Content-Type: application/json

{"some_key":"imsi-460886666660006"}
--21ba7406cff5fa6c192d6b78fe58e16c5fd0cde4111e10f11c2bf55b45a5
Content-Type: application/octet-stream

2e0a00d1
--21ba7406cff5fa6c192d6b78fe58e16c5fd0cde4111e10f11c2bf55b45a5--

这是 erlang shell 中的输出:

1> my_http:send_post_request().
Response:
{ok,{{"HTTP/1.1",200,"OK"},
     [{"connection","keep-alive"},
      {"date","Sun, 21 Jan 2024 02:19:35 GMT"},
      {"etag","W/\"2-eoX0dku9ba8cNUXvu/DyeabcC+s\""},
      {"content-length","2"},
      {"content-type","text/html; charset=utf-8"},
      {"x-powered-by","Express"},
      {"keep-alive","timeout=5"}],
     "ok"}}
ok

祝你好运。您确定您的服务器知道如何解析请求正文吗?如果没有,您可能需要使用

multipart/form-data
的 Content-Type,以及像这样的
Content-Type
标头:

"multipart/form-data;boundary=21ba7406cff5fa6c192d6b78fe58e16c5fd0cde4111e10f11c2bf55b45a5"

请求的正文需要如下所示:

--21ba7406cff5fa6c192d6b78fe58e16c5fd0cde4111e10f11c2bf55b45a5
Content-Disposition: form-data; name="my_json"
Content-Type: application/json  <followed by two newlines>

<JSON HERE>
--21ba7406cff5fa6c192d6b78fe58e16c5fd0cde4111e10f11c2bf55b45a5
Content-Disposition: form-data; name="my_binary_data"
Content-Type: application/octet-stream <followed by two newlines>

<BINARY DATA HERE>
--21ba7406cff5fa6c192d6b78fe58e16c5fd0cde4111e10f11c2bf55b45a5--

典型的服务器会将 json 放在

params["my_json"]
中,将二进制数据放在
params["my_binary_data"]
中。

如果您的二进制数据是 jpeg 文件,您可能会有这样的部分:

--21ba7406cff5fa6c192d6b78fe58e16c5fd0cde4111e10f11c2bf55b45a5
Content-Disposition: form-data; name="my_image"; filename="red_square.jpg"
Content-Type: image/jpeg

r"?>��Adobed����''2&&2.&&&&.>55555>DAAAAAADDDDDDDDDDDDDDDDDDDDDDDDDDDDD  &&6& &6D6++6DDDB5BDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD�}�"��a    !S1A�r��5���
                                                                            ?����-4�f�fbb��a������� �|�G��>v��\��:>��f��)[�֍��6��P�n��yo�� 3��-�C�������#|�@I>�W�������_'�ol���Z�:�_&+ֻ/�Ԙ����
--21ba7406cff5fa6c192d6b78fe58e16c5fd0cde4111e10f11c2bf55b45a5
© www.soinside.com 2019 - 2024. All rights reserved.