blob: 5ebc7b0b6848426b04c9062236eccabef46dfb14 [file] [log] [blame]
//
// ========================================================================
// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.toolchain.test.http;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import java.io.BufferedReader;
import java.io.EOFException;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* A simple http parser implementation to be used in tests. It's feed with a BufferedReader and will read from that
* reader until the http response has been fully read.
*
* It returns a {@link SimpleHttpResponse} object that grants access to the response code,
* headers and body of the parsed response.
*
* This class can easily be extended to provide more features. However keep in mind that it's primary focus is to
* keep parsing as simple as possible.
*/
public class SimpleHttpParser
{
/**
* Reads from the given {@link BufferedReader} and returns the parsed response in a {@link SimpleHttpResponse}
* object.
*
* @param reader
* the inputReader to parse the response from
* @return {@link SimpleHttpResponse} a {@link SimpleHttpResponse} object representing the parsed response
* @throws IOException
* if unable to read/parse the raw lines of http
*/
public SimpleHttpResponse readResponse(BufferedReader reader) throws IOException
{
// Simplified parser for HTTP responses
String line = reader.readLine();
if (line == null)
throw new EOFException();
Matcher responseLine = Pattern.compile("HTTP/1.1" + "\\s+(\\d+)").matcher(line);
assertThat("http version is 1.1",responseLine.lookingAt(),is(true));
String code = responseLine.group(1);
Map<String, String> headers = new LinkedHashMap<String, String>();
while ((line = reader.readLine()) != null)
{
if (line.trim().length() == 0)
break;
parseHeader(line,headers);
}
StringBuilder body;
if (headers.containsKey("content-length"))
{
body = parseContentLengthDelimitedBody(reader,headers);
}
else if ("chunked".equals(headers.get("transfer-encoding")))
{
body = parseChunkedBody(reader);
}
else
{
body = parseEOFDelimitedBody(reader,headers);
}
return new SimpleHttpResponse(code,headers,body.toString().trim());
}
private void parseHeader(String line, Map<String, String> headers)
{
Matcher header = Pattern.compile("([^:]+):\\s*(.*)").matcher(line);
assertTrue(header.lookingAt());
String headerName = header.group(1);
String headerValue = header.group(2);
headers.put(headerName.toLowerCase(),headerValue.toLowerCase());
}
private StringBuilder parseContentLengthDelimitedBody(BufferedReader reader, Map<String, String> headers) throws IOException
{
StringBuilder body;
int readLen = 0;
int length = Integer.parseInt(headers.get("content-length"));
body = new StringBuilder(length);
try
{
//TODO: UTF-8 reader from joakim
for (int i = 0; i < length; ++i)
{
char c = (char)reader.read();
body.append(c);
readLen++;
}
}
catch (SocketTimeoutException e)
{
System.err.printf("Read %,d bytes (out of an expected %,d bytes)%n",readLen,length);
throw e;
}
return body;
}
private StringBuilder parseChunkedBody(BufferedReader reader) throws IOException
{
StringBuilder body;
String line;
body = new StringBuilder(64 * 1024);
while ((line = reader.readLine()) != null)
{
if ("0".equals(line))
{
line = reader.readLine();
assertThat("There's no more content after as 0 indicated the final chunk",line,is(""));
break;
}
int length = Integer.parseInt(line,16);
//TODO: UTF-8 reader from joakim
for (int i = 0; i < length; ++i)
{
char c = (char)reader.read();
body.append(c);
}
reader.readLine();
// assertThat("chunk is followed by an empty line", line, is("")); //TODO: is this right? - NO. Don't
// think you can really do chunks with read line generally, but maybe for this test is OK.
}
return body;
}
private StringBuilder parseEOFDelimitedBody(BufferedReader reader, Map<String, String> headers) throws IOException
{
StringBuilder body;
// read until EOF
body = new StringBuilder();
while (true)
{
//TODO: UTF-8 reader from joakim
int read = reader.read();
if (read == -1)
break;
char c = (char)read;
body.append(c);
}
return body;
}
}