目前我正在开发用 Java 编写的简单文件服务器,它使用套接字进行通信。在这个项目期间,我对 http 请求的格式感兴趣,并希望在我的项目中复制它。我想在低级 api 上执行此操作,仅使用套接字来体验这一切在幕后是如何工作的。
问题非常简单,位于帖子的最后一部分。其他一切都是解释和我对问题的理解。
在下面的示例中,我将使用带有套接字的简化代码来展示我如何理解事物。我还假设存在以下变量:
Socket socket = server.accept();
DataInputStream input = new DataInputStream(socket.getInputStream());
DataOutputStream output = new DataOutputStream(socket.getOutputStream());
好吧,所以解析示例性
application/x-www-form-urlencoded
http 请求(或类似的)对我来说似乎很容易理解,但如果我错了,请纠正我。
有示例请求:
POST / HTTP/1.1
Content-Length: 64
Content-Type: application/x-www-form-urlencoded
name=John%20User&request=Send%20me%20one%20of%20your%20catalogue
示例服务器可以通过这种方式解析此请求:
// read start-line of request
String startLine = input.readline();
...
// read all headers till you encounter empty line
String header;
while (!(header = input.readLine()).equals("")) {
...
}
// read body
int len = <Content-Length header value>;
byte[] body = new byte[len];
input.read(body, 0, len);
...
multipart/form-data
http请求这是我的主要问题。让我们有一个示例性的多部分请求。
POST / HTTP/1.1
Content-Type: multipart/form-data; boundary=boundary
Content-Length: 465
--boundary
Content-Disposition: form-data; name="name"
John
--boundary
Content-Disposition: form-data; name="avatar"; filename="avatar.jpg"
Content-Type: image/jpeg
<some binary data>
--boundary--
我不确定解析这样的请求应该是什么样子。起始行和标题可以按照与前面的示例类似的方式进行解析,但是如何处理正文,特别是当其中有二进制数据时。我有一些想法,但认为它们是错误的/不充分的。
我的尝试是将正文读取为字符串。稍后可以使用边界值将该主体分为多个部分,然后服务器可以处理这些分离的部分(例如提取标头、使用值执行某些操作等)。它可能看起来像这样:
int len = <Content-Length header value>;
byte[] byteBody = new byte[len];
input.read(byteBody, 0, len);
String boundary = <extracted from header>;
String body = new String(byteBody);
String bodyParts = body.split(boundary)
...
然后我遇到了一个问题,它不适用于二进制文件。将
byte[]
转换为 String
,然后再次转换为 byte[]
(在服务器上写入文件)无法用于文件。这是因为默认编码是 ASCII 并且它不支持负值。我做了一个小测试,这是结果。
byte[] arr1 = new byte[] { -1, -2, -3 };
String str1 = new String(arr1);
byte[] arr2 = str1.getBytes();
// arr1 = [-1, -2, -3]
// arr2 = [-17, -65, -67, -17, -65, -67, -17, -65, -67]
获得这些知识后,我寻找解决这个问题的方法。我认为
base64
编码可以解决我的问题,但它对我来说似乎是一种解决方法,并且有其缺点:
base64
。我还找到了很多例子,并做了一个简单的node.js服务器来证明这一点,在
multipart/form-data
请求主体文件肯定可以以二进制格式发送,而不是base64
格式。
我现在有点困惑。我不知道如何解析
multipart/form-data
请求正文,以便我不将其转换为字符串,但仍然可以使用 boundary
的值将其分成单独的部分。我想过逐字节阅读这个正文并以某种方式检测边界,但在我看来这并不是一个好的或有效的方法。
我真的很好奇完成该任务的正确方法是什么以及解析此类请求主体的标准是什么。
正文格式是这样的:
--
后跟 Content-Type 标头中注明的边界\r\n
),类似于根 HTTP 请求的标头和根正文之间的空行--
后跟边界,后跟 --
我不使用 split,而是逐行解析正文;如果遇到边界,您将完成前一部分(第一部分无需执行任何操作)。如果你遇到身体末端,你就完成了。