SDSU CS 580 Client-Server Programming
Fall Semester, 2000
Assignment 1 Solution
Previous    Lecture Notes Index    Next    
© 2000, All Rights Reserved, SDSU & Roger Whitney
San Diego State University -- This page last updated 09-Oct-00

Contents of Doc 13, Assignment 1 Solution



Doc 13, Assignment 1 Solution Slide # 2
Assignment 1 Solution

Problem areas:





Doc 13, Assignment 1 Solution Slide # 3
Some performance Issues

Always make the program work then worry about performance

Profile code to find out were the code is slow

Some Common places that Slow down Java servers

Reading one char per read in a stream

Unicode Strings

new String( byteArray,0)

Avoid synchronized methods when possible


Doc 13, Assignment 1 Solution Slide # 4
System.arraycopy

Primitive to copy arrays


System.arrayCopy(Source, sourceStartIndex, 
   Destination, destinationStartIndex, lengthToCopy )


Doc 13, Assignment 1 Solution Slide # 5

ByteReader & ByteQueue


ByteQueue

Growable queue of bytes
Used in reading data from server


ByteReader

Reads bytes from an input stream
Not a proper Java Reader
Methods:
readUpTo( byte[] pattern)
readThrough( byte[] pattern)
read( int length)
skip( int length)
readAll()
isAtEnd()


Doc 13, Assignment 1 Solution Slide # 6

ByteQueue

package WebProxy;
public class ByteQueue
   {
   byte[] elements; 
   int queueSize = 0;
   
   public ByteQueue() 
      { 
      this(128); 
      }
      
   public ByteQueue( int  size) 
      { 
      elements = new byte[size]; 
      }
   
   public byte[] removeAll()
      {
      return remove(queueSize);
      }
   
   public int size()
      {
      return queueSize;
      }
      
   public void add(byte[] bytes) 
      {
      add( bytes,0, bytes.length);
      }


Doc 13, Assignment 1 Solution Slide # 7
ByteQueue
   public void add(byte[] bytes, int offset, int length) 
      {
      if (queueSize + length > elements.length)
         growelements();
      System.arraycopy(bytes, offset, elements,queueSize, length);
      queueSize = queueSize  + length;
      }
   public byte[] remove( int size) 
      {
      int avaliableSize = Math.min( size, queueSize);
      byte[] response = new byte[avaliableSize];
      System.arraycopy(elements, 0, response, 0,avaliableSize);
      System.arraycopy(elements, avaliableSize, elements, 0,
            queueSize - avaliableSize);
      queueSize = queueSize - avaliableSize;      
      return response;
      }
   
   public boolean contains(byte[] pattern)
      {
      return -1 != indexOf( pattern);
      }
      
   

Doc 13, Assignment 1 Solution Slide # 8
ByteQueue

   public int indexOf( byte[] pattern )
      {
      for (int k = 0; k < queueSize - pattern.length + 1; k++)
         {
         int offset =0;
         while (offset < pattern.length && pattern[offset] == 
               elements[k + offset])
            offset++;
         if (offset == pattern.length)
            return k;
         }
      return -1;
      }
   private void growelements()
      {
      byte[] oldelements = elements;
      elements = new byte[oldelements.length * 2 ];
      System.arraycopy(oldelements, 0, elements, 0,queueSize);
      }
   }

Doc 13, Assignment 1 Solution Slide # 9

Tesing ByteQueue


Sample Test

   public void testQueueIndex()
      {
      ByteQueue queue = new ByteQueue( );
      queue.add( "0123456789112342".getBytes() );
      assert("a",  queue.indexOf( "12".getBytes()) == 1);
      assert("b",  queue.indexOf( "3".getBytes()) == 3);
      assert("c",  queue.indexOf( "6".getBytes()) == 6);
      assert("d",  queue.indexOf( "11".getBytes()) == 10);
      assert("e",  queue.indexOf( "112342".getBytes()) == 10);
      }


Doc 13, Assignment 1 Solution Slide # 10

ByteReader


package WebProxy;
import java.io.*;
public class ByteReader
   {
   static int READ_SIZE = 512;
   static int BUFFER_CAPACITY = 1024 * 4;
   BufferedInputStream in;
   ByteQueue buffer = new ByteQueue(BUFFER_CAPACITY);
   boolean atEOF = false;
   
   public ByteReader( InputStream input)
      {
      in = new BufferedInputStream( input );
      }
   
   public byte[] readUpTo( byte[] pattern) throws IOException
      {
      int location = readToFindIndexOf( pattern);
      return remove( location);
      }
   public byte[] readThrough( byte[] pattern) throws IOException
      {
      int location = readToFindIndexOf( pattern);
      return remove( location + pattern.length);
      }

Doc 13, Assignment 1 Solution Slide # 11
ByteReader

   public byte[] read( int length) throws IOException
      {
      while ( (!isAtEnd() && length > buffer.size()))
         readBlock();
      return remove( length);
      }
      
   public void skip( int length) throws IOException
      {
      read( length);
      }
   public byte[] readAll()  throws IOException
      {
      while (!isAtEnd())
         readBlock();
      return remove( buffer.size());   
      }
      
   public boolean isAtEnd()
      {
      return atEOF;
      }
   private int readToFindIndexOf( byte[] pattern) throws IOException
      {
      int location;
      while ((location = buffer.indexOf( pattern)) == -1)
         readBlock();
      return location;
      }

Doc 13, Assignment 1 Solution Slide # 12
ByteReader
      
   private byte[] remove( int size) throws IOException
      {
      return buffer.remove( size);
      }
      
   private void readBlock() throws IOException
      {
      byte[] readBuffer = new byte[READ_SIZE];
      int bytesRead = in.read( readBuffer);
      if (-1 == bytesRead)
         atEOF = true;
      else
               buffer.add( readBuffer, 0, bytesRead);
      }
   }


Doc 13, Assignment 1 Solution Slide # 13

Sample ByteReader Test


   public void testByteInput() throws IOException
      {
      String response= "caaaat\r\n" +
         "do\rg\r\n\r\nacat";
      ByteReader in = 
         new ByteReader(new StringBufferInputStream( response));
      
      byte[] answer = in.readUpTo("\r\n".getBytes());
      assert( "line 1", (new String( answer)).equals( "caaaat"));
      in.skip( 2);
      answer = in.readThrough("\r\n".getBytes());
      assert( "line 2", (new String( answer)).equals( "do\rg\r\n"));
      in.readThrough("\r\n".getBytes());
      answer = in.readAll();
      assert( "line 3", (new String( answer)).equals( "acat"));
      assert( "end", ( in.isAtEnd()));
      }
   }


Doc 13, Assignment 1 Solution Slide # 14

HttpRequest & HttpResponse


The http request and response have structure, which is similar

Give each a class to parse/create requests and responses

Use super class to leverage common structure


Doc 13, Assignment 1 Solution Slide # 15

HttpTransaction


Super class of HttpRequest & HttpResponse

package WebProxy;
import java.io.*;
import java.util.StringTokenizer;
import java.util.HashMap;
import java.util.Iterator;
public abstract class HttpTransaction
   {
   HashMap headers = new HashMap();
   byte[] body;
   abstract void  parseFirstLine(ByteReader input ); 
   abstract String firstLineToString();
   public void fromStream( InputStream input) throws IOException
      {
      ByteReader bufferedInput = new ByteReader( input);
      
      parseFirstLine(bufferedInput);
      if ( hasHeaders() )
         parseHeaders(bufferedInput);
      if (hasBody() )
         parseBody(bufferedInput);
      }
      

Doc 13, Assignment 1 Solution Slide # 16
HttpTransaction

   public byte[] toBytes()
      {
      if (!hasBody())
         return toString().getBytes();
      String headerString = toString();
      int length = headerString.length() + body.length;
      byte[]  transaction = new byte[length];
      System.arraycopy(headerString.getBytes(), 0, 
         transaction, 0, headerString.length());
      System.arraycopy(body, 0, 
         transaction, headerString.length(), body.length);
      return transaction;
      }
      
   public boolean hasContentLength()
      {
      return (headers.containsKey( "Content-length" ) || 
         headers.containsKey( "Content-Length" ));
      }    
   public int contentLength()
      {
      String length =  null;
      if (headers.containsKey( "Content-length" ) )
         length = (String)headers.get(   "Content-length" );
      else
         length = (String)headers.get( "Content-Length" );
      return Integer.parseInt (length);
      }    

Doc 13, Assignment 1 Solution Slide # 17
HttpTransaction

   public boolean hasBody()
      {
      return hasContentLength();
      }
   
   public byte[] body()
      {
      return body;
      }
      
   void parseBody( ByteReader input) throws IOException
      {
      if (hasContentLength())
         {
         int bodyLength = contentLength();
         body = input.read( bodyLength);
         }
      else
         body = input.readAll();
      }
      
   public String toString()
      {
      if (hasHeaders())
         return firstLineToString() + headerToString();
      else
         return firstLineToString();
      }
   

Doc 13, Assignment 1 Solution Slide # 18
HttpTransaction

   String headerToString()
      {
      StringBuffer headerString = new StringBuffer();
      Iterator keys = headers.keySet().iterator();
      while (keys.hasNext())
         {
         Object key = keys.next();
         Object value = headers.get( key);
         headerString.append(key );
         headerString.append(": " );
         headerString.append(value );
         headerString.append("\r\n");      
         }
      headerString.append("\r\n");   
      return headerString.toString();
      }
      
   public String header( String headerName )
      {
      return (String) headers.get( headerName);
      }
      
   public boolean hasHeaders()
      {
      return true;
      }   
      

Doc 13, Assignment 1 Solution Slide # 19
HttpTransaction

   void parseHeaders( ByteReader input  )
      {
      try
         {
         String requestLine = readLine(input);
         while (requestLine.length() > 0 )
            {
            int separatorIndex = requestLine.indexOf(':');
            String name = requestLine.substring(0, separatorIndex);
            String value = requestLine.substring(separatorIndex + 1).trim();
            headers.put( name, value);
            requestLine = readLine(input);
            }
         }
      catch (IOException readError )
         {
         }
      }
      
   String readLine( ByteReader input ) throws IOException
      {
      byte[] line = input.readUpTo( "\r\n".getBytes());
      input.skip(2);
      return new String( line, 0);
      }
   }

Doc 13, Assignment 1 Solution Slide # 20

HttpResponse


package WebProxy;
import java.io.IOException;
public class HttpResponse extends HttpTransaction
   {
   String statusLine;
   
   public String firstLineToString()
      {
      return statusLine + "\r\n";
      }
         
   void parseFirstLine(ByteReader input)
      {
      try
         {
         statusLine = readLine(input);
         }
      catch (IOException readError )
         {
         }
      }
   public boolean hasBody()
      {
      return true;
      }
   }


Doc 13, Assignment 1 Solution Slide # 21

Sample HttpResponse Test


public void testHTTPResponseNoContentBytes() throws IOException
   {
   String response= "HTTP/1.1 200 OK\r\n" + "Date: hi\r\n\r\nacat";
   HttpResponse request = new HttpResponse();
   request.fromStream( new StringBufferInputStream( response));
   assert( "no content body", 
         (new String( request.body())).equals( "acat"));
   byte[] requestBytes = request.toBytes();
   assert( "no content all", 
         (new String(requestBytes)).equals( response));
   }

Doc 13, Assignment 1 Solution Slide # 22

HttpRequest


package WebProxy;
import java.io.*;
import java.util.StringTokenizer;
import java.net.Socket;
public class HttpRequest extends HttpTransaction
   {
   String requestUri;
   String host;
   int port = 80;
   String requestType;    //GET, POST, etc
   String httpVersion;   
   
   public String firstLineToString()
      {
      StringBuffer request = new StringBuffer();
      request.append( requestType);
      request.append( ' ');
      request.append( requestUri);
      if ( isHttp09() )
         {
         request.append("\r\n");
         return request.toString();
         }
      request.append( ' ');
      request.append( httpVersion);
      request.append("\r\n");
      return request.toString();
      }

Doc 13, Assignment 1 Solution Slide # 23
HttpRequest
   public HttpResponse getResponse() throws IOException
      {
      Socket webServer = new Socket(host, port);
      BufferedOutputStream toServer = 
         new BufferedOutputStream( webServer.getOutputStream() );
      byte[] requestBytes = this.toBytes();
      toServer.write(    requestBytes);
      toServer.flush();
      HttpResponse response = new HttpResponse();
      response.fromStream( webServer.getInputStream() );
      webServer.close();
      return response;
      }
      
   public String getHost(  )
      {
      return host;
      }
   public int getPort( )
      {
      return port ;
      }
   
   public void setHost( String hostName)
      {
      host = hostName;
      }
   public void setPort( int portNumber) { port = portNumber; }

Doc 13, Assignment 1 Solution Slide # 24
HttpRequest

   public void setUri( String pageUri)
      {
      requestUri = reduceURI( pageUri);
      }
      
   public boolean hasHeaders()
      {
      return !isHttp09();
      }
   public boolean isGet()
      {
      if (requestType == null)
         return false;
      return requestType.equals( "GET");
      }
   
   public String getUri()
      {
      return requestUri;
      }
   public boolean isHttp09()
      {
      return httpVersion.equals("0.9");
      }
   public boolean isHttp10()
      {
      return httpVersion.equals("HTTP/1.0");
      }
      

Doc 13, Assignment 1 Solution Slide # 25
HttpRequest

   private String reduceURI(String uri)
      {
      if (uri.toLowerCase().startsWith( "http://") )
         {
         int pathStart = uri.indexOf( "/", 7);
         setPortAndHost( uri.substring(7,pathStart));
         return uri.substring(pathStart);
         }
      return uri;
      }
   
   private void setPortAndHost(String hostPort)
      {
      int portIndicator = hostPort.indexOf( ":");
      if (-1 == portIndicator )
         {
         host = hostPort;
         return;
         }
      host = hostPort.substring(0, portIndicator);
      port = Integer.parseInt( hostPort.substring( portIndicator + 1 ));
      }
         

Doc 13, Assignment 1 Solution Slide # 26
HttpRequest

   private void parseFirstLine(ByteReader input)
      {
      try
         {
         String requestLine = readLine(input);
         StringTokenizer parser = new StringTokenizer( requestLine);
         requestType = parser.nextToken();
         requestUri = reduceURI(parser.nextToken());
         if (parser.hasMoreTokens() )
            httpVersion = parser.nextToken();
         else
            httpVersion = "0.9";
         }
      catch (IOException readError )
         {
         }
      }
   }

Doc 13, Assignment 1 Solution Slide # 27

Sample HttpRequest Test


public void testHTTPBody() throws IOException
   {
   String simpleRequest = "GET /index.html  HTTP/1.0\r\n" +
      "Content-length:  4\r\n\r\nacat";
   HttpRequest request = new HttpRequest();
   request.fromStream( new StringBufferInputStream( simpleRequest));
   assert( "GET", request.isGet() );
   assert( "uri",request.getUri().equals("/index.html"));
   byte[] body = request.body();
   assert( "body", (new String( body)).equals( "acat"));
   }

Doc 13, Assignment 1 Solution Slide # 28

Main Program

Using the Server example in the notes we get our processRequest:


static void processRequest(InputStream in,OutputStream out) 
      throws Exception 
   {
   HttpRequest request = new HttpRequest();
   request.fromStream( in );
   System.out.println(request.toString());
   System.out.println( "Contacting server");
   HttpResponse response = request.getResponse();
   System.out.println( "Response");
   System.out.println( response.toString());
      
   byte[] responseBytes = response.toBytes();
   BufferedOutputStream bufferedOut = 
      new BufferedOutputStream( out );
   bufferedOut.write( responseBytes);
   bufferedOut.flush();
   }



Copyright ©, All rights reserved.
2000 SDSU & Roger Whitney, 5500 Campanile Drive, San Diego, CA 92182-7700 USA.
OpenContent license defines the copyright on this document.

Previous    visitors since 09-Oct-00    Next