/test/src/solidstack/httpclient/Client.java
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}