This is the mail archive of the mauve-patches@sourceware.org mailing list for the Mauve project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

RFC: HTTPTestServer (was: Re: FYI: New tests for HttpURLConnection)


Hi,

Wolfgang Baer wrote:
> Tom Tromey wrote:
>>David Daney had the interesting idea that we could have a mini http
>>server inside of Mauve, which we could then use to test all the http
>>modes we care about.  Specifically what he had suggested was encoding
>>the desired response into the request, so we could check 404s,
>>redirects, chunking, etc.
> 
> 
> Thats definitly the way to go.
> 
>>Just in case you're really motivated to hack in this area ...  :-)
> 
> 
> Not at this time. I just tried to get the bugs fixed I found during
> working on the IPP backend for printing and which prevented me from
> hacking.

Well, as I wanted to test Davids Headers rewrite patch I needed to do
a first hack of a TestHttpServer.

Here we go:

The TestHttpServer has methods to set the Headers and the Body it
should return upon request. Furthermore it provides an interface
which can be implemented to test the serverside received headers and body.

As I am not experienced in this networking area this might be a hack.
However a hack which is working so far :-)

I also wrote two tests to show the usage.

responseCodeTest - test for all error codes the correct handling for
                   getInputStream and getErrorStream.
responseHeadersTest - tests everything with response headers.

2006-03-02  Wolfgang Baer  <WBaer@gmx.de>

	* gnu/testlet/java/net/HttpURLConnection/TestHttpServer.java: New file.
	* gnu/testlet/java/net/HttpURLConnection/responseCodeTest.java:
	New test using TestHttpServer.java.
	* gnu/testlet/java/net/HttpURLConnection/responseHeadersTest.java:
	Likewise.


Comments or OK to commit ?
BTW, whats the best port to use for the TestHttpServer ?

Wolfgang




//Tags: not-a-test

//Copyright (C) 2006 Free Software Foundation, Inc.
//Written by Wolfgang Baer (WBaer@gmx.de)

//This file is part of Mauve.

//Mauve is free software; you can redistribute it and/or modify
//it under the terms of the GNU General Public License as published by
//the Free Software Foundation; either version 2, or (at your option)
//any later version.

//Mauve is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.

//You should have received a copy of the GNU General Public License
//along with Mauve; see the file COPYING.  If not, write to
//the Free Software Foundation, 51 Franklin Street, Fifth Floor,
//Boston, MA, 02110-1301 USA.

package gnu.testlet.java.net.HttpURLConnection;


import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;


/**
 * A HTTP server for testing purpose only. The server can
 * be started on a given port and the response headers and
 * response body to be returned set. This way one can test
 * arbritrary testcases for the http client side library.
 *  
 * @see gnu.testlet.java.net.HttpURLConnection.responseCodeTest
 * @see gnu.testlet.java.net.HttpURLConnection.responseHeadersTest
 * 
 * @author Wolfgang Baer (WBaer@gmx.de)
 */
public final class TestHttpServer implements Runnable
{ 
  /**
   * The interface to be implemented if checking
   * of the serverside received headers and body
   * is desired.
   */
  interface CheckReceivedRequest 
  {
    /**
     * Will be called with a List of headers in 
     * the sequence received by the test server.
     * 
     * @param headers List of headers
     */
    public void checkHeaders(List headers);
    
    /**
     * Will be called with the body if one was
     * received.
     * 
     * @param body the body
     */
    public void checkBody(byte[] body);    
  }
  
  /**
   * The actual request handler.
   * It always returns what is set in the TestHttpServer
   * for the response headers and response body.
   */
  class TestHttpRequestHandler implements Runnable
  {  
    Socket socket;
    OutputStream output;
    InputStream input;
  
    public TestHttpRequestHandler(Socket socket) throws Exception
    {
      this.socket = socket;
      output = socket.getOutputStream();
      input = socket.getInputStream();
    }
  
    public void run()
    {
      try
        {
          // Read the whole request into a byte array
          ByteArrayOutputStream buffer = new ByteArrayOutputStream();
          
          byte[] b = new byte[1024];
          int bytes = 0 ;
          while ((bytes = input.read(b)) != -1)
            { 
              buffer.write(b, 0, bytes);
              if (bytes < 1024)
                break;
            }
          
          byte[] request = buffer.toByteArray();

          // Parse the request headers from the byte array          
          List headerList = new ArrayList();
          
          ByteArrayOutputStream line;
          int i = 0;
          line = new ByteArrayOutputStream();
          for (; i < request.length; i++)
            {
              if (request[i] != (byte) 0x0a) // LF
                line.write(request[i]);
              else
                {
                  byte[] array = line.toByteArray();
                  if (array.length == 1) // the last is only a LF
                    break;

                  String headerLine = new String(array);
                  headerList.add(headerLine);
                  line = new ByteArrayOutputStream();
                }
            }

          // Put the remaining bytes into the request body 
          byte[] body = new byte[(request.length - (i + 1))];
          System.arraycopy(request, i + 1, body, 0, body.length);
          
          // Check everything          
          if (check != null)
            {
              check.checkHeaders(headerList);
              if (body.length > 0)
                check.checkBody(body);
            }         
             
          // Response writing          
          // write the response headers
          output.write(responseHeader);
          
          // write the body 
          if (responseBody != null)
            output.write(responseBody);
          
          // Clean up
          output.close();
          input.close();
          socket.close();            
        }
      catch (Exception e)
        {
          // ignore
        }
    }
  }

  int port;
  byte[] responseHeader;
  byte[] responseBody;
  CheckReceivedRequest check;
  boolean kill = false;
  ServerSocket serverSocket;  

  /**
   * Create a TestHttpServer on given port
   * @param port the port to use.
   */
  public TestHttpServer(int port)
  {
    this.port = port;
  }
  
  /**
   * An object implementing the CheckReceivedRequest
   * interface. The methods will be called to enable
   * checks of the serverside received request.
   * 
   * @param responseBody the byte[] of the body
   */
  public void setCheckReceivedRequest(CheckReceivedRequest object)
  {
    this.check = object;
  }
  
  /**
   * The bytes which should be sent as the response body.
   * @param responseBody the byte[] of the body
   */
  public void setResponseBody(byte[] responseBody)
  {
    this.responseBody = responseBody;
  }
  
  /**
   * The bytes which should be sent as the response headers. 
   * @param responseHeaders the byte[] of the headers
   */
  public void setResponseHeaders(byte[] responseHeaders)
  {
    this.responseHeader = responseHeaders;
  }
  
  /**
   * This cleans up recources so more than one
   * TestHttpServer can be used in one mauve run.
   */
  public void killTestServer()
  {
    kill = true;
    try
      {
        serverSocket.close();
      }
    catch (IOException e)
      {
        // ignore
      }
  }
  
  /**
   * Listens on the port and creates a Handler for
   * incoming connections.
   */
  public void run() 
  {   
    try
      {
        serverSocket = new ServerSocket(port);
        while (! kill)
          {
            Socket socket = serverSocket.accept();
            try
              {
                TestHttpRequestHandler request = 
                  new TestHttpRequestHandler(socket);
                Thread thread = new Thread(request);
                
                thread.start();
              }
            catch (Exception e)
              {
                // ignore
              }
          }
      }
    catch (IOException e)
      {
        // ignore
      }
  }
}
//Tags: JDK1.1
//Uses: TestHttpServer

//Copyright (C) 2006 Free Software Foundation, Inc.
//Written by Wolfgang Baer (WBaer@gmx.de)

//This file is part of Mauve.

//Mauve is free software; you can redistribute it and/or modify
//it under the terms of the GNU General Public License as published by
//the Free Software Foundation; either version 2, or (at your option)
//any later version.

//Mauve is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.

//You should have received a copy of the GNU General Public License
//along with Mauve; see the file COPYING.  If not, write to
//the Free Software Foundation, 51 Franklin Street, Fifth Floor,
//Boston, MA, 02110-1301 USA.

package gnu.testlet.java.net.HttpURLConnection;

import gnu.testlet.TestHarness;
import gnu.testlet.Testlet;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;

/**
 * Tests correct behaviour of getInputStream(), getErrorStream()
 * for all error response codes.
 */
public class responseCodeTest implements Testlet
{
  /**
   * Starts an HTTP server on port 8080 and calls
   * the test_ResponseCode for the error codes.
   */
  public void test(TestHarness h) 
  {  
    TestHttpServer server = null;
    try
      {
        server = new TestHttpServer(8080);
        Thread thread = new Thread(server);
        thread.start();
        try
          {
            Thread.sleep(500);
          }
        catch (InterruptedException e1)
          {
          }
        
        for (int i=400; i < 418; i++)
          test_ResponseCode(i, h, server);
        
        for (int i=500; i < 506; i++)
          test_ResponseCode(i, h, server);
      }
    finally
      {
        server.killTestServer();
      }   
  }
  
  public void test_ResponseCode(int responseCode, 
      TestHarness h, TestHttpServer server)
  {    
    try
      {        
        URL url = new URL("http://localhost:8080/";);        
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();        
        conn.setRequestMethod("GET");
        
        h.checkPoint("Test " + responseCode + " response");

        // construct what should be returned by the test server as headers        
        ByteArrayOutputStream headers = new ByteArrayOutputStream();

        // status line (the responsecode encoded for the test)
        headers.write(new String("HTTP/1.0 " + responseCode + " OK\r\n").getBytes());
        headers.write("Server: TestServer\r\n\r\n".getBytes());

        server.setResponseHeaders(headers.toByteArray());
        
        // test the responsecode
        int code = conn.getResponseCode();
        h.check(code == responseCode);

        // getInputStream should always throw an IOException
        try 
          {
            conn.getInputStream();
            h.check(false);
          }
        catch (IOException e)
          {
            // for a 404/410 it must be a FNFE
            if (responseCode == 404 
                || responseCode == 410)
              {
                if (e instanceof FileNotFoundException)
                  h.check(true);
                else
                  h.check(false);
              }
            else
              h.check(true);
          }
        
        // the errorstream must be set always
        InputStream error = conn.getErrorStream();
        h.check(error != null);
        
        conn.disconnect();       
      }   
    catch (IOException e)
      {       
        h.debug("Unexpected IOException");
        h.debug(e);
      }
  }
}
//Tags: JDK1.4
//Uses: TestHttpServer

//Copyright (C) 2006 Free Software Foundation, Inc.
//Written by Wolfgang Baer (WBaer@gmx.de)

//This file is part of Mauve.

//Mauve is free software; you can redistribute it and/or modify
//it under the terms of the GNU General Public License as published by
//the Free Software Foundation; either version 2, or (at your option)
//any later version.

//Mauve is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.

//You should have received a copy of the GNU General Public License
//along with Mauve; see the file COPYING.  If not, write to
//the Free Software Foundation, 51 Franklin Street, Fifth Floor,
//Boston, MA, 02110-1301 USA.

package gnu.testlet.java.net.HttpURLConnection;

import gnu.testlet.TestHarness;
import gnu.testlet.Testlet;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.List;
import java.util.Map;

public class responseHeadersTest implements Testlet
{

  public void test(TestHarness harness)
  {
    TestHttpServer server = null;
    try
      {
        server = new TestHttpServer(8080);
        Thread thread = new Thread(server);
        thread.start();
        try
          {
            Thread.sleep(500);
          }
        catch (InterruptedException e1)
          {
          }
        
        test_MultiHeaders(harness, server);
      }
    finally
      {
        server.killTestServer();
      }   
  }
  
  public void test_MultiHeaders(TestHarness h, TestHttpServer server)
  {    
    try
      {        
        URL url = new URL("http://localhost:8080/";);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("GET");

        // construct what should be returned by the test server as headers
        ByteArrayOutputStream headers = new ByteArrayOutputStream();
        headers.write("HTTP/1.0 200 OK\r\n".getBytes());
        headers.write("Server: TestServer\r\n".getBytes());
        headers.write("Key1: value, value2\r\n".getBytes());
        // set the header a second time with different values
        // these values must be prepended to key1
        headers.write("Key1: value3\r\n".getBytes());
        headers.write("IntHeader: 1234\r\n".getBytes());
        headers.write("IntHeaderMalformed: 1234XY\r\n".getBytes());
        headers.write("DateHeader: Thu, 02 Mar 2006 14:34:55 +0000\r\n".getBytes());
        headers.write("DateHeaderMalformed: Thu, 02 Mar 2006V 14:13:07 +0000\r\n\r\n".getBytes());

        server.setResponseHeaders(headers.toByteArray());
        
        h.checkPoint("getHeaderFields()");
        Map fields = conn.getHeaderFields();
        
        // check that map is unmodifiable
        try 
          {
            fields.clear();
            h.check(false);
          }
        catch (UnsupportedOperationException e) 
          {
            h.check(true);
          }
      
        // exactly 7 headers with status and server header        
        h.check(fields.size() == 7);
        
        // check for list and that case matters for key
        Object obj = fields.get("Key1");
        if (! (obj instanceof List))
          h.check(false);
        else
          {
            h.check(true);
            List value = (List) obj;
            h.check(value.size() == 2);
            h.check(value.get(0).equals("value3"));
            h.check(value.get(1).equals("value, value2"));
            
            // check that it is an unmodifiable list
            try 
              {
                value.remove(0);
                h.check(false);
              }
            catch (UnsupportedOperationException e) 
              {
                h.check(true);
              }
          }
        
        // wrong case for key
        obj = fields.get("key1");
        h.check(obj == null);
          
        
        // checks for getHeaderField/Key(int)        
        h.checkPoint("getHeaderField(int)");
        // check that index 0 is the statusline
        String statusline = conn.getHeaderField(0);
        h.check(statusline.equals("HTTP/1.0 200 OK"));
        // indexes out of bound must return null
        String aboutIndex = conn.getHeaderField(44);
        h.check(aboutIndex == null);
        String belowIndex = conn.getHeaderField(-1);
        h.check(belowIndex == null);
        // check that correct key/value name is returned
        String key1_Value = conn.getHeaderField(2);
        h.check(key1_Value.equals("value, value2"));
        
        h.checkPoint("getHeaderFieldKey(int)");       
        // check that index 0 is the statusline
        String statuslineKey = conn.getHeaderFieldKey(0);        
        h.check(statuslineKey == null);
        // indexes out of bound must return null
        String aboutIndexKey = conn.getHeaderFieldKey(44);        
        h.check(aboutIndexKey == null);
        String belowIndexKey = conn.getHeaderFieldKey(-1);
        h.check(belowIndexKey == null);
        // check that correct key/value name is returned
        String key1_Key = conn.getHeaderFieldKey(2);
        h.check(key1_Key.equals("Key1"));
        
        
        // checks getHeaderFieldDate
        h.checkPoint("getHeaderFieldDate()");
        // correct date header field
        long dateHeader = conn.getHeaderFieldDate("DateHeader", 5555);
        h.check(dateHeader == 1141310095000L);
        // missing date header field
        dateHeader = conn.getHeaderFieldDate("DateHeaderXX", 5555);
        h.check(dateHeader == 5555);
        // malformed date header value 
        dateHeader = conn.getHeaderFieldDate("DateHeaderMalformed", 5555);
        h.check(dateHeader == 5555);
        
        // checks getHeaderFieldInt
        h.checkPoint("getHeaderFieldInt()");
        // correct int header field
        int intHeader = conn.getHeaderFieldInt("IntHeader", 5555);
        h.check(intHeader == 1234);
        // missing int header field
        intHeader = conn.getHeaderFieldInt("IntHeaderXX", 5555);
        h.check(intHeader == 5555);
        // malformed int header value 
        intHeader = conn.getHeaderFieldInt("IntHeaderMalformed", 5555);
        h.check(intHeader == 5555);
        
        
        // checks that the convenience methods of the headers
        // not set in this test return the correct default values
        h.checkPoint("convenience methods");
        h.check(conn.getLastModified() == 0);
        h.check(conn.getDate() == 0);
        h.check(conn.getExpiration() == 0);
        h.check(conn.getContentEncoding() == null);
        h.check(conn.getContentType() == null);
        h.check(conn.getContentLength() == -1);
        
        
        // checks getHeaderField(String)
        h.checkPoint("getHeaderField(String)");
        String field = conn.getHeaderField("Server");
        String field1 = conn.getHeaderField("server");
        h.check(field.equals("TestServer"));
        h.check(field == field1);
        
        String none = conn.getHeaderField("NotExistent");
        h.check(none == null);
        
        // check for multiple times same key
        String field_key1 = conn.getHeaderField("Key1");
        h.check(field_key1.equals("value3"));

      }
    catch (IOException e)
    {
      h.debug("Unexpected IOException");
      h.debug(e);
    }
    catch (RuntimeException e)
      {
        h.debug("Unexpected IOException");
        h.debug(e);
      }
    
  }

}

Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]