我目前正在 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.
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--
注意最后一个边界上的尾随“--”。您必须手工组装所有这些。
请求正文中出现的实际边界由以下部分组成:
初始回车+换行,称为
CRLF
,由字符“组成”
“(这是两个八位字节)。省略正文中第一个边界的初始 CRLF。
两个字符“--”。
Content-Type 标头中指定的边界。
终止 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