我试图使用Socket在Java中处理一个简单的POST请求。 我可以收到请求标头并毫无问题地回答请求,但我当然无法得到请求的正文。
我在某个地方读过我需要打开第二个InputStream来实现这个目标,但这对我来说并不合适。您有关于如何获得请求正文的任何提示吗?
这是我基本上用来获取标题的内容:
BufferedReader in = new BufferedReader(new InputStreamReader(
clientSocket.getInputStream()));
char[] inputBuffer = new char[INPUT_BUFFER_LENGTH];
int inputMessageLength = in.read(inputBuffer, 0,
INPUT_BUFFER_LENGTH);
String inputMessage = new String(inputBuffer, 0, inputMessageLength);
所以,我得到的信息是:
POST / HTTP/1.1
User-Agent: Java/1.8.0_45
Host: localhost:5555
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
但我无法获取POST请求的参数。
编辑:
所以事实证明我只有 INPUT_BUFFER_LENGTH 足够高(我知道,对我感到羞耻)。 因此,当我工作时,我将 ServerSocket 更改为 SSLServerSocket 并再次尝试从Java发送带有 HttpsUrlConnection 的请求,现在我遇到了同样的问题再次(已经检查过缓冲区),得到这样的东西:
POST / HTTP/1.1
User-Agent: Java/1.8.0_45
Host: localhost:5555
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Connection: keep-alive
Content-type: application/x-www-form-urlencoded
Content-Length: 128
*Missing Body*
事实证明,我只是在使用我的Java客户端发送请求时才得到这个 - 来自Chrome等的发送请求工作正常 - 所以我认为我的代码中出了问题。 这是我用来发送请求的内容:
System.setProperty("javax.net.ssl.trustStore", ...);
System.setProperty("javax.net.ssl.trustStorePassword", ...);
SSLSocketFactory socketFactory = (SSLSocketFactory) SSLSocketFactory
.getDefault();
String url = "https://...";
URL obj = new URL(url);
HttpsURLConnection con = (HttpsURLConnection) obj.openConnection();
HttpsURLConnection.setDefaultSSLSocketFactory(socketFactory);
con.setRequestMethod("POST");
con.setDoOutput(true);
OutputStreamWriter writer = new OutputStreamWriter(con.getOutputStream());
writer.write(*Some String*);
writer.flush();
writer.close();
有关我的代码可能出错的任何提示吗?
答案 0 :(得分:4)
您显示的代码不是读取HTTP请求的正确方法。
首先,Java有自己的HttpServer
和HttpsServer
类。你应该考虑使用它们。
否则,您必须手动实施HTTP协议。您需要逐行阅读输入,直到您到达指示请求标头结尾的空行,然后查看您已阅读的标头,特别是Transfer-Encoding
和Content-Length
标头,知道如何根据RFC 2616 Section 4.4:
4.4消息长度
消息的传输长度是消息体的长度 它出现在信息中;也就是说,在任何转移编码之后 已被应用。当消息中包含消息正文时, 该身体的转移长度由以下之一决定 (按优先顺序排列):
任何“绝不”包含消息体的响应消息(例如 作为1xx,204和304响应以及对HEAD的任何响应 请求)总是在第一个空行后终止 标题字段,不管实体标题字段是否存在 消息。
如果存在Transfer-Encoding标头字段(第14.41节) 除了“身份”之外还有任何其他值,那么转移长度是 通过使用“分块”转移编码(第3.6节)来定义, 除非通过关闭连接终止消息。
如果存在Content-Length标头字段(第14.13节),则为其 OCTET中的十进制值表示实体长度和 转发长度。不得发送Content-Length头字段 如果这两个长度不同(即,如果转移编码 标题字段存在)。如果收到包含a的消息 Transfer-Encoding标头字段和Content-Length标头字段, 后者必须被忽略。
如果消息使用媒体类型“multipart / byteranges”,则为 没有另外规定转移长度,那么这个自我 消除媒体类型定义传输长度。这种媒体类型 除非发件人知道收件人可以屁股,否则不得使用UST 它;在具有多个字节的Range标头的请求中的存在 - 1.1客户端的范围说明符暗示客户端可以解析 multipart / byteranges响应。
范围标头可能由1.0代理转发 理解multipart / byteranges;在这种情况下,服务器必须 使用第1,3或5项中定义的方法来分隔消息 本节。
- 醇>
由服务器关闭连接。 (关闭连接 因此,不能用于表示请求正文的结束 不会让服务器发回回复。)
为了与HTTP / 1.0应用程序,HTTP / 1.1请求兼容 包含消息体必须包含有效的Content-Length头 字段,除非已知服务器符合HTTP / 1.1。如果一个 请求包含一个消息体,并且没有给出Content-Length, 如果不能,服务器应该响应400(错误的请求) 确定消息的长度,或者使用411(需要的长度)if 它希望坚持收到有效的内容长度。
接收实体的所有HTTP / 1.1应用程序必须接受 “分块”转移编码(第3.6节),从而允许这种机制 在无法确定消息长度时用于消息 提前。
消息不得包含Content-Length头字段和a 非身份转移编码。如果消息确实包含非 身份转移编码,必须忽略内容长度。
在消息正文的消息中给出Content-Length时 允许,其字段值必须与OCTET的数量完全匹配 消息体。 HTTP / 1.1用户代理必须在何时通知用户 收到并检测到无效长度。
尝试更像这样的东西(半伪代码):
String readLine(BufferedInputStream in)
{
// HTTP carries both textual and binary elements.
// Not using BufferedReader.readLine() so it does
// not "steal" bytes from BufferedInputStream...
// HTTP itself only allows 7bit ASCII characters
// in headers, but some header values may be
// further encoded using RFC 2231 or 5987 to
// carry Unicode characters ...
InputStreamReader r = new InputStreamReader(in, StandardCharsets.US_ASCII);
StringBuilder sb = new StringBuilder();
char c;
while ((c = r.read()) >= 0) {
if (c == '\n') break;
if (c == '\r') {
c = r.read();
if ((c < 0) || (c == '\n')) break;
sb.append('\r');
}
sb.append(c);
}
return sb.toString();
}
...
BufferedInputStream in = new BufferedInputStream(clientSocket.getInputStream());
String request = readLine(in);
// extract method, resource, and version...
String line;
do
{
line = readLine(in);
if (line.isEmpty()) break;
// store line in headers list...
}
while (true);
// parse headers list...
if (request method has a message-body) // POST, etc
{
if ((request version >= 1.1) &&
(Transfer-Encoding header is present) &&
(Transfer-Encoding != "identity"))
{
// read chunks...
do
{
line = readLine(in); // read chunk header
int size = extract value from line;
if (size == 0) break;
// use in.read() to read the specified
// number of bytes into message-body...
readLine(in); // skip trailing line break
}
while (true);
// read trailing headers...
line = readLine(in);
while (!line.isEmpty())
{
// store line in headers list, updating
// any existing header as needed...
}
// parse headers list again ...
}
else if (Content-Length header is present)
{
// use in.read() to read the specified
// number of bytes into message-body...
}
else if (Content-Type is "multipart/...")
{
// use readLine(in) and in.read() as needed
// to read/parse/decode MIME encoded data into
// message-body until terminating MIME boundary
// is reached...
}
else
{
// fail the request...
}
}
// process request and message-body as needed..