Search code examples
javahttpapache-httpcomponents

How do I get the body from this example HTTP request?


I'm trying to find the simplest way to parse an RFC-822 document in Java. Assume that I have a message-queue on which HTTP messages are stored. Both requests and responses. So they are not retrieved in the "normal" way by making a socket-connection to - say - port 80 and sending/retrieving the message from there.

In the code below, I deliberately mixed "mail" headers with a HTTP message. It's meant as a demonstration that the two are not very different. But that's beside the point. Here's the code:

package httpexample;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import org.apache.http.Header;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.impl.io.DefaultHttpRequestParser;
import org.apache.http.impl.io.HttpTransportMetricsImpl;
import org.apache.http.impl.io.SessionInputBufferImpl;
import org.apache.http.io.HttpMessageParser;
import org.apache.http.message.BasicHttpEntityEnclosingRequest;

public class HttpExample {

    // RFC 822

    public static void main(String[] args) throws IOException, HttpException {
        String str = "POST http://localhost:8080/foobar/1234567 HTTP/1.1\n" +
            "Message-ID: <19815303.1075861029555.JavaMail.ss@kk>\n" +
            "Date: Wed, 6 Mar 2010 12:32:20 -0800 (PST)\n" +
            "From: [email protected]\n" +
            "To: [email protected]\n" +
            "Subject: some subject\n" +
            "Mime-Version: 1.0\n" +
            "Content-Type: text/plain; charset=us-ascii\n" +
            "Content-Transfer-Encoding: 7bit\n" +
            "X-From: one, some <[email protected]>\n" +
            "X-To: one\n" +
            "X-cc: \n" +
            "X-bcc: \n" +
            "X-Origin: Bob-R\n" +
            "X-FileName: rbob (Non-Privileged).pst\n" +
            "\n" +
            "some message\n";
        ByteArrayInputStream fakeStream = new ByteArrayInputStream(
                str.getBytes());
        HttpTransportMetricsImpl metrics = new HttpTransportMetricsImpl();
        SessionInputBufferImpl inbuffer = new SessionInputBufferImpl(metrics, 1024);

        inbuffer.bind(fakeStream);
        HttpMessageParser<HttpRequest> requestParser =
                new DefaultHttpRequestParser(inbuffer);
        BasicHttpEntityEnclosingRequest request = (BasicHttpEntityEnclosingRequest)requestParser.parse();

        for (Header hdr : request.getAllHeaders()) {
            System.out.println(String.format("%-30s = %s", hdr.getName(), hdr.getValue()));
        }
        System.out.println(String.format("Request Line: %s", request.getRequestLine()));
        System.out.println(String.format("Body\n------------------\n%s",
                request.getEntity()));
    }

}

The output looks like this:

Message-ID                     = <19815303.1075861029555.JavaMail.ss@kk>
Date                           = Wed, 6 Mar 2010 12:32:20 -0800 (PST)
From                           = [email protected]
To                             = [email protected]
Subject                        = some subject
Mime-Version                   = 1.0
Content-Type                   = text/plain; charset=us-ascii
Content-Transfer-Encoding      = 7bit
X-From                         = one, some <[email protected]>
X-To                           = one
X-cc                           = 
X-bcc                          = 
X-Origin                       = Bob-R
X-FileName                     = rbob (Non-Privileged).pst
Request Line: POST http://localhost:8080/foobar/1234567 HTTP/1.1
Body
------------------
null

What I can't figure out, is how to access the body of the message.

I would expect it to have the content some message\n

I can't find any method in BasicHttpEntityEnclosingRequest that would give me this value. In an earlier version I used

HttpRequest request = requestParser.parse();

instead of

BasicHttpEntityEnclosingRequest request = 
    (BasicHttpEntityEnclosingRequest) requestParser.parse();

I changed it to BasicHttpEntityEnclosingRequest because that has the getEntity method. But that returns null.

So I'm a bit lost.

Where do I find the body?


Solution

  • I have added Content-Length header, otherwise the parser simply ignores the POST body. I have modified your code, now it parses the body just fine:

    package org.apache.http.examples;
    
    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.net.Socket;
    
    import org.apache.http.Header;
    import org.apache.http.HttpException;
    import org.apache.http.message.BasicHttpEntityEnclosingRequest;
    import org.apache.http.util.EntityUtils;
    
    public class HttpExample {
    
        // RFC 822
    
        public static void main(String[] args) throws IOException, HttpException {
            String str = "POST http://localhost:8080/foobar/1234567 HTTP/1.1\n" +
                "Message-ID: <19815303.1075861029555.JavaMail.ss@kk>\n" +
                "Date: Wed, 6 Mar 2010 12:32:20 -0800 (PST)\n" +
                "From: [email protected]\n" +
                "To: [email protected]\n" +
                "Subject: some subject\n" +
                "Mime-Version: 1.0\n" +
                "Content-Type: text/plain; charset=us-ascii\n" +
                "Content-Transfer-Encoding: 7bit\n" +
                "X-From: one, some <[email protected]>\n" +
                "X-To: one\n" +
                "X-cc: \n" +
                "X-bcc: \n" +
                "X-Origin: Bob-R\n" +
                "X-FileName: rbob (Non-Privileged).pst\n" +
                "Content-Length: 13\n" +
                "\n" +
                "some message\n";
            ByteArrayInputStream fakeStream = new ByteArrayInputStream(
                    str.getBytes());
    
            BHttpConnectionBaseImpl b = new BHttpConnectionBaseImpl(fakeStream);
    
            BasicHttpEntityEnclosingRequest request1 = (BasicHttpEntityEnclosingRequest) b.receiveRequestHeader();
            b.receiveRequestEntity(request1);
    
    
            for (Header hdr : request1.getAllHeaders()) {
                System.out.println(String.format("%-30s = %s", hdr.getName(), hdr.getValue()));
            }
            System.out.println(String.format("Request Line: %s", request1.getRequestLine()));
            System.out.println(String.format("Body\n------------------\n%s",
                    EntityUtils.toString( request1.getEntity() ) ));
        }
    
    }
    
    class BHttpConnectionBaseImpl extends  org.apache.http.impl.DefaultBHttpServerConnection{
    
        private InputStream inputStream;
    
        public BHttpConnectionBaseImpl(final InputStream inputStream) {
            super(4048);
            this.inputStream = inputStream;
            try {
                super.bind(new Socket());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        @Override
        protected InputStream getSocketInputStream(final Socket socket) throws IOException {
            return inputStream;
        }
    
        @Override
        protected OutputStream getSocketOutputStream(final Socket socket) throws IOException {
            return new ByteArrayOutputStream();
        }
    }
    

    The parsing of POST body happens in org.apache.http.impl.BHttpConnectionBase.prepareInput(HttpMessage), whoever its only constructor is protected and requires a lot of parameters. The child org.apache.http.impl.DefaultBHttpServerConnection has a convenient public constructor and does the header parsing in receiveRequestHeader(). The methods I'm overloading are need to bypass some error checks, e.g. if the Socket == null and to be able to read the request from the fakeStream

    Another approach that might working, although I have not tested it, is to override Socket particularly its getInputStream() and getOutputStream(). Then create an instance of DefaultBHttpServerConnection and call its bind method. The rest should be the same.