PageRenderTime 23ms CodeModel.GetById 14ms app.highlight 7ms RepoModel.GetById 0ms app.codeStats 0ms

/test/src/solidstack/httpclient/Client.java

http://solidstack.googlecode.com/
Java | 201 lines | 155 code | 21 blank | 25 comment | 18 complexity | ebbd2f80d98e9aa9b0955da0806ca6d3 MD5 | raw file
Possible License(s): Apache-2.0
  1package solidstack.httpclient;
  2
  3import java.io.IOException;
  4import java.io.InputStream;
  5import java.io.OutputStream;
  6import java.io.PrintStream;
  7import java.net.Socket;
  8import java.util.ArrayList;
  9import java.util.List;
 10import java.util.Map;
 11import java.util.concurrent.Semaphore;
 12
 13import solidstack.httpserver.HttpBodyInputStream;
 14import solidstack.httpserver.HttpException;
 15import solidstack.httpserver.HttpHeaderTokenizer;
 16import solidstack.httpserver.Token;
 17import solidstack.io.FatalIOException;
 18import solidstack.lang.ThreadInterrupted;
 19
 20public class Client extends Thread
 21{
 22	private String hostname;
 23	private Semaphore semaphore;
 24	private List<Socket> sockets = new ArrayList<Socket>();
 25
 26	public Client( String hostname ) throws IOException
 27	{
 28		this.hostname = hostname;
 29		this.semaphore = new Semaphore( 4 );
 30	}
 31
 32	public Socket connect() throws IOException
 33	{
 34		try
 35		{
 36			this.semaphore.acquire();
 37		}
 38		catch( InterruptedException e )
 39		{
 40			throw new ThreadInterrupted();
 41		}
 42		boolean finished = false;
 43		Socket socket = null;
 44		try
 45		{
 46			synchronized( this.sockets )
 47			{
 48				if( !this.sockets.isEmpty() )
 49					socket = this.sockets.remove( 0 );
 50			}
 51			if( socket == null || !socket.isConnected() )
 52				socket = new Socket( this.hostname, 80 );
 53			finished = true;
 54			return socket;
 55		}
 56		finally
 57		{
 58			if( !finished )
 59			{
 60				this.semaphore.release();
 61				if( socket != null )
 62					socket.close();
 63			}
 64		}
 65	}
 66
 67	public void release( Socket socket )
 68	{
 69		if( socket.isConnected() )
 70			synchronized( this.sockets )
 71			{
 72				this.sockets.add( socket );
 73			}
 74		this.semaphore.release();
 75	}
 76
 77	public void sendRequest( Request request, OutputStream out )
 78	{
 79		RequestWriter writer = new RequestWriter( out, "ISO-8859-1" );
 80		writer.write( "GET " );
 81		String path = request.getPath();
 82		writer.write( path.length() > 0 ? path : "/" );
 83		writer.write( " HTTP/1.1\r\n" );
 84		for( Map.Entry< String, List< String > > entry : request.getHeaders().entrySet() )
 85			for( String value : entry.getValue() )
 86			{
 87				writer.write( entry.getKey() );
 88				writer.write( ": " );
 89				writer.write( value );
 90				writer.write( "\r\n" );
 91//				System.out.println( "    " + entry.getKey() + " = " + value );
 92			}
 93		writer.write( "\r\n" );
 94		writer.flush();
 95	}
 96
 97	public Response receiveResponse( InputStream in )
 98	{
 99		Response result = new Response();
100
101		HttpHeaderTokenizer tokenizer = new HttpHeaderTokenizer( in );
102
103		String line = tokenizer.getLine();
104		String[] parts = line.split( "[ \t]+" );
105
106		if( !parts[ 0 ].equals( "HTTP/1.1" ) )
107			throw new HttpException( "Only HTTP/1.1 responses are supported" );
108
109		result.setHttpVersion( parts[ 0 ] );
110		result.setStatus( Integer.parseInt( parts[ 1 ] ) );
111		result.setReason( parts[ 2 ] );
112
113		Token field = tokenizer.getField();
114		while( !field.isEndOfInput() )
115		{
116			Token value = tokenizer.getValue();
117//			System.out.println( "    "+ field.getValue() + " = " + value.getValue() );
118			result.addHeader( field.getValue(), value.getValue() );
119			field = tokenizer.getField();
120		}
121
122		String length = result.getHeader( "Content-Length" );
123		if( length != null )
124		{
125			int l = Integer.parseInt( length );
126			result.setInputStream( new HttpBodyInputStream( in, l ) );
127		}
128		else
129		{
130			String encoding = result.getHeader( "Transfer-Encoding" );
131			if( "chunked".equals( encoding ) )
132//				result.setInputStream( in );
133				result.setInputStream( new ChunkedInputStream( in ) );
134		}
135
136		// TODO Which error codes do not contain a body?
137
138		return result;
139
140		// TODO Detect Connection: close headers on the request & response
141		// TODO What about socket.getKeepAlive() and the other properties?
142
143//		String length = getHeader( "Content-Length" );
144//		Assert.notNull( length );
145//		int l = Integer.parseInt( length );
146//		this.bodyIn = new HttpBodyInputStream( in, l );
147
148//		if( length == null )
149//		{
150//			String transfer = response.getHeader( "Transfer-Encoding" );
151//			if( !"chunked".equals( transfer ) )
152//				this.socket.close();
153//		}
154//
155//		if( !this.socket.isClosed() )
156//			if( request.isConnectionClose() )
157//				this.socket.close();
158//		if( this.socket.isClosed() )
159//			return;
160//		if( !this.socket.isThreadPerConnection() )
161//			if( in.available() <= 0 )
162//				return;
163	}
164
165	public void request( Request request, ResponseProcessor responseProcessor ) throws IOException
166	{
167		Socket socket = connect();
168		try
169		{
170			sendRequest( request, socket.getOutputStream() );
171			Response response = receiveResponse( socket.getInputStream() );
172			InputStream in = response.getInputStream();
173			responseProcessor.process( response );
174			drain( in, System.out );
175		}
176		finally
177		{
178			release( socket );
179		}
180	}
181
182	private void drain( InputStream in, PrintStream out )
183	{
184		if( in == null )
185			return;
186		try
187		{
188			int i = in.read();
189			while( i >= 0 )
190			{
191				if( out != null )
192					out.print( (char)i );
193				i = in.read();
194			}
195		}
196		catch( IOException e )
197		{
198			throw new FatalIOException( e );
199		}
200	}
201}