在 Java 中通过 Socket 处理 POST 请求
Handling POST request via Socket in Java
我正在尝试使用套接字在 Java 中处理一个简单的 POST 请求。
我可以收到请求header并毫无问题地回答请求,但是我肯定无法得到请求的body。
我在某处读到我需要打开第二个 InputStream 才能实现此目的,但这对我来说真的没有意义。您对如何获取请求有任何提示吗body?
这就是我用来获取 header:
的基本方法
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 并再次尝试使用 HttpsUrlConnection 发送请求从 Java 开始,现在我又遇到了同样的问题(已经检查了缓冲区),结果如下:
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-Client 发送请求时才会收到此信息 - 从 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();
关于我的代码可能有什么问题的任何提示?
您显示的代码不是读取 HTTP 请求的正确方法。
首先,Java 有自己的 HttpServer
and HttpsServer
类。您应该考虑使用它们。
否则,您必须手动实现 HTTP 协议。您需要阅读输入 line-by-line 直到到达表示请求结束的空行 headers,然后查看您已阅读的 headers,尤其是 Transfer-Encoding
和 Content-Length
headers,根据 RFC 2616 Section 4.4:
了解如何读取请求的剩余字节
4.4 Message Length
The transfer-length of a message is the length of the message-body as
it appears in the message; that is, after any transfer-codings have
been applied. When a message-body is included with a message, the
transfer-length of that body is determined by one of the following
(in order of precedence):
- Any response message which "MUST NOT" include a message-body (such
as the 1xx, 204, and 304 responses and any response to a HEAD
request) is always terminated by the first empty line after the
header fields, regardless of the entity-header fields present in
the message.
- If a Transfer-Encoding header field (section 14.41) is present and
has any value other than "identity", then the transfer-length is
defined by use of the "chunked" transfer-coding (section 3.6),
unless the message is terminated by closing the connection.
- If a Content-Length header field (section 14.13) is present, its
decimal value in OCTETs represents both the entity-length and the
transfer-length. The Content-Length header field MUST NOT be sent
if these two lengths are different (i.e., if a Transfer-Encoding
header field is present). If a message is received with both a
Transfer-Encoding header field and a Content-Length header field,
the latter MUST be ignored.
- If the message uses the media type "multipart/byteranges", and the
ransfer-length is not otherwise specified, then this self-
elimiting media type defines the transfer-length. This media type
UST NOT be used unless the sender knows that the recipient can arse
it; the presence in a request of a Range header with ultiple byte-
range specifiers from a 1.1 client implies that the lient can parse
multipart/byteranges responses.
A range header might be forwarded by a 1.0 proxy that does not
understand multipart/byteranges; in this case the server MUST
delimit the message using methods defined in items 1,3 or 5 of
this section.
- By the server closing the connection. (Closing the connection
cannot be used to indicate the end of a request body, since that
would leave no possibility for the server to send back a response.)
For compatibility with HTTP/1.0 applications, HTTP/1.1 requests
containing a message-body MUST include a valid Content-Length header
field unless the server is known to be HTTP/1.1 compliant. If a
request contains a message-body and a Content-Length is not given,
the server SHOULD respond with 400 (bad request) if it cannot
determine the length of the message, or with 411 (length required) if
it wishes to insist on receiving a valid Content-Length.
All HTTP/1.1 applications that receive entities MUST accept the
"chunked" transfer-coding (section 3.6), thus allowing this mechanism
to be used for messages when the message length cannot be determined
in advance.
Messages MUST NOT include both a Content-Length header field and a
non-identity transfer-coding. If the message does include a non-
identity transfer-coding, the Content-Length MUST be ignored.
When a Content-Length is given in a message where a message-body is
allowed, its field value MUST exactly match the number of OCTETs in
the message-body. HTTP/1.1 user agents MUST notify the user when an
invalid length is received and detected.
尝试更像这样的东西(semi-pseudo 代码):
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..
我正在尝试使用套接字在 Java 中处理一个简单的 POST 请求。 我可以收到请求header并毫无问题地回答请求,但是我肯定无法得到请求的body。
我在某处读到我需要打开第二个 InputStream 才能实现此目的,但这对我来说真的没有意义。您对如何获取请求有任何提示吗body?
这就是我用来获取 header:
的基本方法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 并再次尝试使用 HttpsUrlConnection 发送请求从 Java 开始,现在我又遇到了同样的问题(已经检查了缓冲区),结果如下:
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-Client 发送请求时才会收到此信息 - 从 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();
关于我的代码可能有什么问题的任何提示?
您显示的代码不是读取 HTTP 请求的正确方法。
首先,Java 有自己的 HttpServer
and HttpsServer
类。您应该考虑使用它们。
否则,您必须手动实现 HTTP 协议。您需要阅读输入 line-by-line 直到到达表示请求结束的空行 headers,然后查看您已阅读的 headers,尤其是 Transfer-Encoding
和 Content-Length
headers,根据 RFC 2616 Section 4.4:
4.4 Message Length
The transfer-length of a message is the length of the message-body as it appears in the message; that is, after any transfer-codings have been applied. When a message-body is included with a message, the transfer-length of that body is determined by one of the following (in order of precedence):
- Any response message which "MUST NOT" include a message-body (such as the 1xx, 204, and 304 responses and any response to a HEAD request) is always terminated by the first empty line after the header fields, regardless of the entity-header fields present in the message.
- If a Transfer-Encoding header field (section 14.41) is present and has any value other than "identity", then the transfer-length is defined by use of the "chunked" transfer-coding (section 3.6), unless the message is terminated by closing the connection.
- If a Content-Length header field (section 14.13) is present, its decimal value in OCTETs represents both the entity-length and the transfer-length. The Content-Length header field MUST NOT be sent if these two lengths are different (i.e., if a Transfer-Encoding header field is present). If a message is received with both a Transfer-Encoding header field and a Content-Length header field, the latter MUST be ignored.
- If the message uses the media type "multipart/byteranges", and the ransfer-length is not otherwise specified, then this self- elimiting media type defines the transfer-length. This media type UST NOT be used unless the sender knows that the recipient can arse it; the presence in a request of a Range header with ultiple byte- range specifiers from a 1.1 client implies that the lient can parse multipart/byteranges responses.
A range header might be forwarded by a 1.0 proxy that does not understand multipart/byteranges; in this case the server MUST delimit the message using methods defined in items 1,3 or 5 of this section.
- By the server closing the connection. (Closing the connection cannot be used to indicate the end of a request body, since that would leave no possibility for the server to send back a response.)
For compatibility with HTTP/1.0 applications, HTTP/1.1 requests containing a message-body MUST include a valid Content-Length header field unless the server is known to be HTTP/1.1 compliant. If a request contains a message-body and a Content-Length is not given, the server SHOULD respond with 400 (bad request) if it cannot determine the length of the message, or with 411 (length required) if it wishes to insist on receiving a valid Content-Length.
All HTTP/1.1 applications that receive entities MUST accept the "chunked" transfer-coding (section 3.6), thus allowing this mechanism to be used for messages when the message length cannot be determined in advance.
Messages MUST NOT include both a Content-Length header field and a non-identity transfer-coding. If the message does include a non- identity transfer-coding, the Content-Length MUST be ignored.
When a Content-Length is given in a message where a message-body is allowed, its field value MUST exactly match the number of OCTETs in the message-body. HTTP/1.1 user agents MUST notify the user when an invalid length is received and detected.
尝试更像这样的东西(semi-pseudo 代码):
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..