通过Java中的Socket处理POST请求

时间:2015-06-17 19:55:15

标签: java sockets http post request

我试图使用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();

有关我的代码可能出错的任何提示吗?

1 个答案:

答案 0 :(得分:4)

您显示的代码不是读取HTTP请求的正确方法。

首先,Java有自己的HttpServerHttpsServer类。你应该考虑使用它们。

否则,您必须手动实施HTTP协议。您需要逐行阅读输入,直到您到达指示请求标头结尾的空行,然后查看您已阅读的标头,特别是Transfer-EncodingContent-Length标头,知道如何根据RFC 2616 Section 4.4

读取请求的剩余字节数
  

4.4消息长度

     

消息的传输长度是消息体的长度      它出现在信息中;也就是说,在任何转移编码之后      已被应用。当消息中包含消息正文时,      该身体的转移长度由以下之一决定      (按优先顺序排列):

     
      
  1. 任何“绝不”包含消息体的响应消息(例如    作为1xx,204和304响应以及对HEAD的任何响应    请求)总是在第一个空行后终止    标题字段,不管实体标题字段是否存在    消息。

  2.   
  3. 如果存在Transfer-Encoding标头字段(第14.41节)    除了“身份”之外还有任何其他值,那么转移长度是    通过使用“分块”转移编码(第3.6节)来定义,    除非通过关闭连接终止消息。

  4.   
  5. 如果存在Content-Length标头字段(第14.13节),则为其    OCTET中的十进制值表示实体长度和    转发长度。不得发送Content-Length头字段    如果这两个长度不同(即,如果转移编码    标题字段存在)。如果收到包含a的消息    Transfer-Encoding标头字段和Content-Length标头字段,    后者必须被忽略。

  6.   
  7. 如果消息使用媒体类型“multipart / byteranges”,则为    没有另外规定转移长度,那么这个自我    消除媒体类型定义传输长度。这种媒体类型    除非发件人知道收件人可以屁股,否则不得使用UST    它;在具有多个字节的Range标头的请求中的存在 -    1.1客户端的范围说明符暗示客户端可以解析    multipart / byteranges响应。

         

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

  8.   
  9. 由服务器关闭连接。 (关闭连接    因此,不能用于表示请求正文的结束    不会让服务器发回回复。)

         

    为了与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用户代理必须在何时通知用户   收到并检测到无效长度。

  10.   

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

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