//
// ========================================================================
// Copyright (c) 1995-2017 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.http;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/**
* A HTTP Testing helper class.
*
* Example usage:
* <pre>
* try(Socket socket = new Socket("www.google.com",80))
* {
* HttpTester.Request request = HttpTester.newRequest();
* request.setMethod("POST");
* request.setURI("/search");
* request.setVersion(HttpVersion.HTTP_1_0);
* request.put(HttpHeader.HOST,"www.google.com");
* request.put("Content-Type","application/x-www-form-urlencoded");
* request.setContent("q=jetty%20server");
* ByteBuffer output = request.generate();
*
* socket.getOutputStream().write(output.array(),output.arrayOffset()+output.position(),output.remaining());
* HttpTester.Input input = HttpTester.from(socket.getInputStream());
* HttpTester.Response response = HttpTester.parseResponse(input);
* System.err.printf("%s %s %s%n",response.getVersion(),response.getStatus(),response.getReason());
* for (HttpField field:response)
* System.err.printf("%s: %s%n",field.getName(),field.getValue());
* System.err.printf("%n%s%n",response.getContent());
* }
* </pre>
*/
public class HttpTester
{
private final static Logger LOG = Log.getLogger(HttpTester.class);
private HttpTester()
{
}
public static Request newRequest()
{
Request r=new Request();
r.setMethod(HttpMethod.GET.asString());
r.setURI("/");
r.setVersion(HttpVersion.HTTP_1_1);
return r;
}
public static Request parseRequest(String request)
{
Request r=new Request();
HttpParser parser =new HttpParser(r);
parser.parseNext(BufferUtil.toBuffer(request));
return r;
}
public static Request parseRequest(ByteBuffer request)
{
Request r=new Request();
HttpParser parser =new HttpParser(r);
parser.parseNext(request);
return r;
}
public static Response parseResponse(String response)
{
Response r=new Response();
HttpParser parser =new HttpParser(r);
parser.parseNext(BufferUtil.toBuffer(response));
return r;
}
public static Response parseResponse(ByteBuffer response)
{
Response r=new Response();
HttpParser parser =new HttpParser(r);
parser.parseNext(response);
return r;
}
public static Response parseResponse(InputStream responseStream) throws IOException
{
ByteArrayOutputStream contentStream = new ByteArrayOutputStream();
IO.copy(responseStream, contentStream);
Response r=new Response();
HttpParser parser =new HttpParser(r);
parser.parseNext(ByteBuffer.wrap(contentStream.toByteArray()));
return r;
}
public abstract static class Input
{
final ByteBuffer _buffer;
boolean _eof=false;
HttpParser _parser;
Input()
{
this(BufferUtil.allocate(8192));
}
Input(ByteBuffer buffer)
{
_buffer = buffer;
}
public ByteBuffer getBuffer()
{
return _buffer;
}
public void setHttpParser(HttpParser parser)
{
_parser=parser;
}
public HttpParser getHttpParser()
{
return _parser;
}
public HttpParser takeHttpParser()
{
HttpParser p=_parser;
_parser=null;
return p;
}
public boolean isEOF()
{
return BufferUtil.isEmpty(_buffer) && _eof;
}
public abstract int fillBuffer() throws IOException;
}
public static Input from(final ByteBuffer data)
{
return new Input(data.slice())
{
@Override
public int fillBuffer() throws IOException
{
_eof=true;
return -1;
}
};
}
public static Input from(final InputStream in)
{
return new Input()
{
@Override
public int fillBuffer() throws IOException
{
BufferUtil.compact(_buffer);
int len=in.read(_buffer.array(),_buffer.arrayOffset()+_buffer.limit(),BufferUtil.space(_buffer));
if (len<0)
_eof=true;
else
_buffer.limit(_buffer.limit()+len);
return len;
}
};
}
public static Input from(final ReadableByteChannel in)
{
return new Input()
{
@Override
public int fillBuffer() throws IOException
{
BufferUtil.compact(_buffer);
int pos=BufferUtil.flipToFill(_buffer);
int len=in.read(_buffer);
if (len<0)
_eof=true;
BufferUtil.flipToFlush(_buffer,pos);
return len;
}
};
}
public static Response parseResponse(Input in) throws IOException
{
Response r;
HttpParser parser=in.takeHttpParser();
if (parser==null)
{
r=new Response();
parser = new HttpParser(r);
}
else
r=(Response)parser.getHandler();
parseResponse(in, parser, r);
if(r.isComplete())
return r;
in.setHttpParser(parser);
return null;
}
public static void parseResponse(Input in, Response response) throws IOException
{
HttpParser parser = in.takeHttpParser();
if (parser == null)
{
parser = new HttpParser(response);
}
parseResponse(in, parser, response);
if (!response.isComplete())
in.setHttpParser(parser);
}
private static void parseResponse(Input in, HttpParser parser, Response r) throws IOException
{
ByteBuffer buffer = in.getBuffer();
while(true)
{
if (BufferUtil.hasContent(buffer))
if (parser.parseNext(buffer))
break;
int len=in.fillBuffer();
if (len==0)
break;
if (len<=0)
{
parser.atEOF();
parser.parseNext(buffer);
break;
}
}
}
public abstract static class Message extends HttpFields implements HttpParser.HttpHandler
{
boolean _earlyEOF;
boolean _complete=false;
ByteArrayOutputStream _content;
HttpVersion _version=HttpVersion.HTTP_1_0;
public boolean isComplete()
{
return _complete;
}
public HttpVersion getVersion()
{
return _version;
}
public void setVersion(String version)
{
setVersion(HttpVersion.CACHE.get(version));
}
public void setVersion(HttpVersion version)
{
_version=version;
}
public void setContent(byte[] bytes)
{
try
{
_content=new ByteArrayOutputStream();
_content.write(bytes);
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
public void setContent(String content)
{
try
{
_content=new ByteArrayOutputStream();
_content.write(StringUtil.getBytes(content));
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
public void setContent(ByteBuffer content)
{
try
{
_content=new ByteArrayOutputStream();
_content.write(BufferUtil.toArray(content));
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
public byte[] getContentBytes()
{
if (_content==null)
return null;
return _content.toByteArray();
}
public String getContent()
{
if (_content==null)
return null;
byte[] bytes=_content.toByteArray();
String content_type=get(HttpHeader.CONTENT_TYPE);
String encoding=MimeTypes.getCharsetFromContentType(content_type);
Charset charset=encoding==null?StandardCharsets.UTF_8:Charset.forName(encoding);
return new String(bytes,charset);
}
@Override
public void parsedHeader(HttpField field)
{
add(field.getName(),field.getValue());
}
@Override
public boolean contentComplete()
{
return false;
}
@Override
public boolean messageComplete()
{
_complete=true;
return true;
}
@Override
public boolean headerComplete()
{
_content=new ByteArrayOutputStream();
return false;
}
@Override
public void earlyEOF()
{
_earlyEOF = true;
}
public boolean isEarlyEOF()
{
return _earlyEOF;
}
@Override
public boolean content(ByteBuffer ref)
{
try
{
_content.write(BufferUtil.toArray(ref));
}
catch (IOException e)
{
throw new RuntimeException(e);
}
return false;
}
@Override
public void badMessage(int status, String reason)
{
throw new RuntimeException(reason);
}
public ByteBuffer generate()
{
try
{
HttpGenerator generator = new HttpGenerator();
MetaData info = getInfo();
// System.err.println(info.getClass());
// System.err.println(info);
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteBuffer header=null;
ByteBuffer chunk=null;
ByteBuffer content=_content==null?null:ByteBuffer.wrap(_content.toByteArray());
loop: while(!generator.isEnd())
{
HttpGenerator.Result result = info instanceof MetaData.Request
?generator.generateRequest((MetaData.Request)info,header,chunk,content,true)
:generator.generateResponse((MetaData.Response)info,false,header,chunk,content,true);
switch(result)
{
case NEED_HEADER:
header=BufferUtil.allocate(8192);
continue;
case NEED_CHUNK:
chunk=BufferUtil.allocate(HttpGenerator.CHUNK_SIZE);
continue;
case NEED_CHUNK_TRAILER:
chunk=BufferUtil.allocate(8192);
continue;
case NEED_INFO:
throw new IllegalStateException();
case FLUSH:
if (BufferUtil.hasContent(header))
{
out.write(BufferUtil.toArray(header));
BufferUtil.clear(header);
}
if (BufferUtil.hasContent(chunk))
{
out.write(BufferUtil.toArray(chunk));
BufferUtil.clear(chunk);
}
if (BufferUtil.hasContent(content))
{
out.write(BufferUtil.toArray(content));
BufferUtil.clear(content);
}
break;
case SHUTDOWN_OUT:
break loop;
}
}
return ByteBuffer.wrap(out.toByteArray());
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
abstract public MetaData getInfo();
@Override
public int getHeaderCacheSize()
{
return 0;
}
}
public static class Request extends Message implements HttpParser.RequestHandler
{
private String _method;
private String _uri;
@Override
public boolean startRequest(String method, String uri, HttpVersion version)
{
_method=method;
_uri=uri.toString();
_version=version;
return false;
}
public String getMethod()
{
return _method;
}
public String getUri()
{
return _uri;
}
public void setMethod(String method)
{
_method=method;
}
public void setURI(String uri)
{
_uri=uri;
}
@Override
public MetaData.Request getInfo()
{
return new MetaData.Request(_method,new HttpURI(_uri),_version,this,_content==null?0:_content.size());
}
@Override
public String toString()
{
return String.format("%s %s %s\n%s\n",_method,_uri,_version,super.toString());
}
public void setHeader(String name, String value)
{
put(name,value);
}
}
public static class Response extends Message implements HttpParser.ResponseHandler
{
private int _status;
private String _reason;
@Override
public boolean startResponse(HttpVersion version, int status, String reason)
{
_version=version;
_status=status;
_reason=reason;
return false;
}
public int getStatus()
{
return _status;
}
public String getReason()
{
return _reason;
}
@Override
public MetaData.Response getInfo()
{
return new MetaData.Response(_version,_status,_reason,this,_content==null?-1:_content.size());
}
@Override
public String toString()
{
return String.format("%s %s %s\n%s\n",_version,_status,_reason,super.toString());
}
}
}