在 Java 中通过 Socket 处理 POST 请求 [英] Handling POST request via Socket in Java

查看:53
本文介绍了在 Java 中通过 Socket 处理 POST 请求的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用 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用户代理:Java/1.8.0_45主机:本地主机:5555接受:文本/html、图像/gif、图像/jpeg、*;q=.2, */*;q=.2

但是我无法获取POST请求的参数.

所以结果证明我只是将 INPUT_BUFFER_LENGTH 升高到足够高(我知道,我很丢脸).因此,当它工作时,我将我的 ServerSocket 更改为 SSLServerSocket 并再次尝试从 Java 发送带有 HttpsUrlConnection 的请求,现在我遇到了同样的问题再次(已经检查了缓冲区),得到如下内容:

POST/HTTP/1.1用户代理:Java/1.8.0_45主机:本地主机:5555接受:文本/html、图像/gif、图像/jpeg、*;q=.2, */*;q=.2连接:保持连接内容类型:application/x-www-form-urlencoded内容长度:128*失踪的身体*

结果我只在用我的 Java 客户端发送请求时才得到这个 - 从 Chrome 发送请求等工作正常 - 所以我认为我的代码有问题.这是我用来发送请求的内容:

System.setProperty("javax.net.ssl.trustStore", ...);System.setProperty("javax.net.ssl.trustStorePassword", ...);SSLSocketFactory socketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();String url = "https://...";URL obj = 新 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 有自己的 HttpServerHttpsServer 类.您应该考虑使用它们.

否则,您必须手动实现 HTTP 协议.您需要逐行读取输入,直到到达指示请求标头结束的空行,然后查看已读取的标头,尤其是 Transfer-EncodingContent-Length 标头,了解如何读取请求的剩余字节,根据 RFC 2616 第 4.4 节:

<块引用>

4.4 消息长度

<块引用>

消息的传输长度是消息体的长度为它出现在消息中;也就是说,在任何传输编码有被应用.当消息中包含消息正文时,该主体的传输长度由以下之一确定(按优先顺序):

<块引用>

  1. 任何不得"的响应消息包括一个消息体(例如作为 1xx、204 和 304 响应以及对 HEAD 的任何响应request) 总是以第一个空行结束标头字段,无论实体标头字段存在于消息.

<块引用>

  1. 如果存在 Transfer-Encoding 标头字段(第 14.41 节)并且具有除identity"以外的任何值,则传输长度为通过使用chunked"来定义的传输编码(第 3.6 节),除非通过关闭连接来终止消息.

<块引用>

  1. 如果一个 Content-Length 头域(14.13 节)存在,它的OCTET 中的十进制值表示实体长度和传输长度.不得发送 Content-Length 头字段如果这两个长度不同(即,如果传输编码头字段存在).如果收到的消息同时包含Transfer-Encoding 头域和 Content-Length 头域,后者必须被忽略.

<块引用>

  1. 如果消息使用媒体类型multipart/byteranges",并且传输长度没有另外指定,那么这个自限制媒体类型定义传输长度.这种媒体类型不得使用除非发件人知道收件人可以它;请求中存在具有多个字节的 Range 标头来自 1.1 客户端的范围说明符意味着客户端可以解析多部分/字节范围响应.

<块引用>

范围标头可能由 1.0 代理转发了解多部分/字节范围;在这种情况下,服务器必须使用第 1,3 或 5 项中定义的方法分隔消息本节.

<块引用>

  1. 由服务器关闭连接.(关闭连接不能用于指示请求正文的结束,因为不会让服务器发回响应.)

<块引用>

为了兼容 HTTP/1.0 应用,HTTP/1.1 请求包含一个消息体必须包含一个有效的 Content-Length 头字段,除非已知服务器符合 HTTP/1.1.如果一个请求包含消息正文,但未给出内容长度,如果不能,服务器应该响应 400(错误的请求)确定消息的长度,或者使用 411(需要长度),如果它希望坚持接收有效的 Content-Length.

<块引用>

所有接收实体的 HTTP/1.1 应用程序必须接受分块"传输编码(第 3.6 节),从而允许这种机制当无法确定消息长度时用于消息提前.

<块引用>

消息不能同时包含一个 Content-Length 头域和一个非身份传输编码.如果消息确实包含非身份传输编码,必须忽略内容长度.

<块引用>

当在消息中给出内容长度时,消息体是允许,其字段值必须与中的 OCTET 数量完全匹配消息体.HTTP/1.1 用户代理必须通知用户接收并检测到无效长度.

尝试更像这样的东西(半伪代码):

String readLine(BufferedInputStream in){//HTTP 携带文本和二进制元素.//不使用 BufferedReader.readLine() 所以它使用//不是偷"来自 BufferedInputStream 的字节...//HTTP 本身只允许 7 位 ASCII 字符//在标题中,但某些标题值可能是//使用 RFC 2231 或 5987 进一步编码到//携带 Unicode 字符 ...InputStreamReader r = new InputStreamReader(in, StandardCharsets.US_ASCII);StringBuilder sb = new StringBuilder();字符 c;而((c = r.read())> = 0){if (c == '
') 中断;如果 (c == '
') {c = r.read();if ((c <0) || (c == '
')) 中断;sb.append('
');}sb.append(c);}返回 sb.toString();}...BufferedInputStream in = new BufferedInputStream(clientSocket.getInputStream());字符串请求 = readLine(in);//提取方法、资源和版本...字符串线;做{线 = 读取线(输入);如果 (line.isEmpty()) 中断;//将行存储在标题列表中...}同时(真);//解析标题列表...if (request method has a message-body)//POST 等{if ((请求版本>= 1.1) &&(存在传输编码标头)&&(传输编码!=身份")){//读取块...做{线 = 读取线(输入);//读取块头int size = 从行中提取值;如果(大小== 0)中断;//使用 in.read() 读取指定的//消息体的字节数...读取线(输入);//跳过尾随换行符}同时(真);//读取尾随标题...线 = 读取线(输入);while (!line.isEmpty()){//在标题列表中存储行,更新//根据需要任何现有的标题...}//再次解析标题列表...}else if(存在内容长度标头){//使用 in.read() 读取指定的//消息体的字节数...}else if (Content-Type 是multipart/..."){//根据需要使用 readLine(in) 和 in.read()//读取/解析/解码 MIME 编码的数据到//消息体直到终止 MIME 边界//到达了...}别的{//请求失败...}}//根据需要处理请求和消息体..

I'm trying to handle a simple POST Request in Java using a Socket. I can receive the request header and answer the request without any problem, but I certainly can not get the body of the request.

I read somewhere that I'd need to open a second InputStream to achive this, but this doesn't really makes sense to me. Do you have any tips on how to get the request body?

This is what I basically use to get the 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);

So, the message I get is something like:

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

But I can't get the parameters of the POST request.

Edit:

So it turned out I just had INPUT_BUFFER_LENGTH up high enough (I know, shame on me). So as it worked I changed my ServerSocket to SSLServerSocket and tried again to send a request with a HttpsUrlConnection from Java, now I have the same problem again (already checked the buffer), getting something like this:

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*

It turned out I only get this when sending requests with my Java-Client - Sending requests from Chrome, etc are working fine - so I assume I got something wrong in my code. This is what I use to send the request:

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();

Any tips on what might be wrong with my code?

解决方案

The code you have shown is not the correct way to read HTTP requests.

First off, Java has its own HttpServer and HttpsServer classes. You should consider using them.

Otherwise, you have to implement the HTTP protocol manually. You need to read the input line-by-line until you reach an empty line indicating the end of the request headers, then look at the headers you have read, in particular the Transfer-Encoding and Content-Length headers, to know how to read the remaining bytes of the request, per 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):

  1. 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.

  1. 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.

  1. 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.

  1. 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.

  1. 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.

Try something more like this (semi-pseudo code):

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 == '
') break;
        if (c == '
') {
            c = r.read();
            if ((c < 0) || (c == '
')) break;
            sb.append('
');
        }
        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 中通过 Socket 处理 POST 请求的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆