/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

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