Package org.w3c.www.protocol.http

Source Code of org.w3c.www.protocol.http.HttpBasicServer

// HttpBasicServer.java
// $Id: HttpBasicServer.java,v 1.73 2005/06/03 09:13:35 ylafon Exp $
// (c) COPYRIGHT MIT and INRIA, 1996.
// Please first read the full copyright statement in file COPYRIGHT.html

package org.w3c.www.protocol.http;

import java.util.Date;

import java.io.BufferedInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;

import java.net.InetAddress;
import java.net.UnknownHostException;

import org.w3c.www.mime.MimeHeaderHolder;
import org.w3c.www.mime.MimeParser;
import org.w3c.www.mime.MimeParserException;
import org.w3c.www.mime.MimeParserFactory;

import org.w3c.www.http.HTTP;
import org.w3c.www.http.HttpEntityMessage;
import org.w3c.www.http.HttpMessage;
import org.w3c.www.http.HttpReplyMessage;
import org.w3c.www.http.HttpRequestMessage;
import org.w3c.www.http.HttpStreamObserver;
import org.w3c.www.http.ChunkedOutputStream;


/**
* The basic server class, to run requests.
* A server instance (ie an object conforming to the <code>HttpServer</code>
* interface is resposnsible for running all requests to a given host.
* <p>To do so, it manages a connnnection pool (ie a set of available
* connections) which it negotiates with the global HTTP manager. It keeps
* track of the connections it creates in order to serialize requests
* to the target server (when possible). In order to avoid deadlock due
* to implicit ordering of requests, a server that manages persistent
* (ie > 1.0) connections will always be able to open at least two
* connections to the same target.
* <p>Connections are kept track of by the server instance, which maintains
* at all point in time a list of <em>idle</em> connections. The way this is
* done may seem quite tricky, but beleive me, it's the best way I have
* found to handle it (there is here a typical deadlock situation - the same
* which is found in org.w3c.jigsaw.http.Client
* - due to the fact that the server instances need to be locked in order
* to change the connection pool, and at the same time each connection of
* the pool might have to notify the pool  of some events. This can easily
* produce a deadlock...This situation is avoided here, by having the server
* locked only when appropriate...
*/

public class HttpBasicServer extends HttpServer {
    private static final String
    STATE_CONNECTION = "org.w3c.www.protocol.http.HttpBasicServer.connection";

    private static final String PROTOCOL = "http";
    private static final boolean debug = false;

    /**
     * Request mode - Full HTTP/1.1 compliant mode.
     */
    protected final static int RQ_HTTP11 = 1;
    /**
     * Request mode - Full two stage HTTP/1.1 compliant mode.
     */
    protected final static int RQ_HTTP11_TS = 2;
    /**
     * Request mode - HTTP/1.0 with no keep-alive support.
     */
    protected final static int RQ_HTTP10 = 3;
    /**
     * Request mode - HTTP/1.0 with keep-alive support.
     */
    protected final static int RQ_HTTP10_KA = 4;
    /**
     * Request mode - Unknown target server.
     */
    protected final static int RQ_UNKNOWN = 5;

    /**
     * Our central HTTP manager.
     */
    protected HttpManager manager = null;
    /**
     * The host name of the server we handle.
     */
    protected String host = null;
    /**
     * The port number of the server we handle.
     */
    protected int port = -1;
    /**
     * The timeout on the socket
     */
    protected int timeout = 300000;
    /**
     * The connectiontimeout for the socket
     */
    protected int conn_timeout = 3000;

    // Informations pertaining to the server:
    boolean contacted    = false;
    short   major        = -1;
    short   minor        = -1;
    boolean keepalive    = false;

    InetAddress addrs[]     = null;
    int         addrptr     = 0;
    Date        lookupLimit = null;

    /**
     * set the inet addresses of this host, and timestamp it
     * to avoid caching IPs forever
     * @param hostAddrs, an array of InetAddress
     */
    protected void setHostAddr(InetAddress hostAddrs[]) {
  this.addrs = hostAddrs;
  // reset the pointer in case the number of ip diminished
  addrptr = 0;
  // FIXME now defaults to 300s (5mn) but needs to be a parameter
  lookupLimit = new Date(System.currentTimeMillis() + 300 * 1000 );
    }

    /**
     * check the validity of the host address
     * if invalid, it will update the InetAddress associated with this host
     */
    protected void updateHostAddr()
  throws HttpException
    {
  Date now = new Date();
  if ((lookupLimit == null) || lookupLimit.before(now)) {
      // Get this server IP addreses:
      try {
    InetAddress hostAddrs[];
    // Is the given string a valid IP address ?
    int     hostlen  = host.length();
    boolean isstring = true;
    for (int i = 0 ; i < hostlen ; i++) {
        char c = host.charAt(i);
        if (isstring = !
      (((c <= '9') && (c >= '0')) || (c == '.'))) {
      break;
        }
    }
    if ( isstring ) {
        // String host name, get all IP addresses:
        hostAddrs = InetAddress.getAllByName(host);
    } else {
        // Numeric IP address, convert to InetAddress:
        hostAddrs    = new InetAddress[1];
        hostAddrs[0] = InetAddress.getByName(host);
    }
    if (hostAddrs != null) {
        setHostAddr(hostAddrs);
    }
      } catch (UnknownHostException ex) {
    String msg = ("The host name ["+host+"] couldn't be resolved. "
            + "Details: \""+ex.getMessage()+"\"");
    HttpException hex = new HttpException(ex, msg);
    state.ex = hex;
    state.state = HttpServerState.ERROR;
    throw hex;
      }
  }
    }
     
    /**
     * HttpServer implementation - Get this servers' protocol.
     * @return A String encoding the protocol used to dialog with the target
     * server.
     */

    public String getProtocol() {
  return PROTOCOL;
    }

    /**
     * HttpServer implementation - Get this server's major version number.
     * @return The server's major number version, or <strong>-1</strong>
     * if still unknown.
     */

    public short getMajorVersion() {
  return major;
    }

    /**
     * HttpServer implementation - Get this server's minor version number.
     * @return The server's minor number version, or <strong>-1</strong>
     * if still unknown.
     */

    public short getMinorVersion() {
  return minor;
    }

    /**
     * Set the timeout for the next connections
     * @param timeout The timeout in milliseconds
     */

    public synchronized void setTimeout(int timeout) {
  this.timeout = timeout;
    }

    /**
     * Set the connection timeout for the next connections
     * @param timeout The timeout in milliseconds
     */

    public synchronized void setConnTimeout(int conn_timeout) {
  this.conn_timeout = conn_timeout;
    }

    /**
     * Connections management - Allocate a new connection for this server.
     * The connection is bound to the next available IP address, so that
     * we are able to round-robin on them. If one of the DNS advertized
     * IP address fails, we just try the next one, until one of them
     * succeed or all of them fail.
     * @return A freshly allocated connection, inserted in the idle connection
     * list.
     * @exception IOException If the server is unreachable through all of
     * Its IP addresses.
     */

    protected int connid = 0;

    protected HttpBasicConnection allocateConnection()
  throws IOException
    {
  // Create the new connection, try until all ip addrs have been checked
  HttpBasicConnection conn = null;
  try {
      updateHostAddr();
  } catch (HttpException hex) {
      // unable to update, will continue with the old version
      // as it may be a temporary DNS problem
      if (addrs == null) {
    throw new IOException(hex.getMessage());
      }
  }
  for (int loop = 0 ; loop < addrs.length ; loop++) {
      InetAddress addr = null;
      int         i    = addrptr;
      do {
    if ((addr = addrs[i]) != null)
        break;
    i = (i+1) % addrs.length;
      } while ( i != addrptr );
      addrptr = (addrptr+1) % addrs.length;
      if (addr == null) {
    throw new IOException("Host "+host+" resolved to a null"
              + " InetAddr");
      }
      try {
    int l_connid;
    synchronized (this) {
        l_connid = connid++;
    }
    conn = new HttpBasicConnection(this
                 , l_connid
                 , addr
                 , port
                 , timeout
                 , conn_timeout
                 , manager.getReplyFactory());
    break;
      } catch (IOException ex) {
    ex.printStackTrace();
      }
  }
  // If successfull, register the connection as one of ours:
  if ( conn == null )
      throw new IOException("Unable to connect to "+host);
  synchronized (manager) {
      state.incrConnectionCount();
//      state.allconns.add(conn); (used for debug)
      manager.notifyConnection(conn);
  }
  return conn;
    }

    /**
     * Connections management - Register a connection as being idle.
     * When a connection is created, or when it becomes idle again (after
     * having been used to handle a request), it has to be registered back to
     * the idle list of connection for the server. All connections
     * contained in this idle list are candidates for being elected to handle
     * some pending or incoming request to the host we manager.
     * @param conn The connection that is now idle.
     */

    public void registerConnection(HttpConnection conn) {
  synchronized (manager) {
      state.registerConnection(conn);
      manager.notifyIdle(conn);
  }
    }

    /**
     * Unregister a connection from the idle list.
     * Unregistering a connection means that the server shouldn't keep
     * track of it any more. This can happen in two situations:
     * <ul>
     * <li>The connection won't be reusable, so there is no point
     * for the server to try to keep track of it. In this case, the
     * connection is forgotten, and the caller will terminate it by invoking
     * the connection's input stream close method.
     * <li>The connection has successfully handle a connection, and the
     * connection is about to be reused. During the time of the request
     * processing, the server looses track of this connection, which will
     * register itself again when back to idle.
     * @param conn The connection to unregister from the idle list.
     */

    public synchronized void unregisterConnection(HttpConnection conn) {
  manager.notifyUse(conn);
    }

    public void deleteConnection(HttpConnection conn) {
  synchronized (manager) {
      state.decrConnectionCount();
//      state.allconns.remove(conn); (used for debug)
      state.unregisterConnection(conn);
      manager.deleteConnection(conn);
  }
    }

    /**
     * Connections management - Get an idle connection to run a request.
     * The server has been asked to run a new request, and it now wants
     * a connection to run it on. This method will try various ways of
     * aqcuiring a connection:
     * <ul>
     * <li>It will look for an idle connection.
     * <li>It will then try to negotiate with the HTTP manager the creation
     * of a new connection to the target server.
     * <li>If this fails too, it will just wait until a connection becomes
     * available.
     * </ul>
     * The connection returned is marked for use (ie it is unregistered from
     * the idle connection list), it is up to the caller to make sure that
     * if possible, the connection registers itself again to the idle list
     * after the processing is done.
     * <p>This method can return <strong>null</strong> to indicate to the
     * caller that it should try again, in the hope that the target
     * server has multiple (different) IP addresses.
     * @return A connection marked in use, and which should be marked as
     * idle after the processing it is use for is done, or <strong>null
     * </strong> if a fresh connection cannot be established.
     */

    protected HttpBasicConnection getConnection()
  throws IOException
    {
  int _waits = 0;
  while ( _waits < 3 ) {
      // Ask for a spare connection first:
      HttpBasicConnection conn = null;
      while ( true ) {
    conn = (HttpBasicConnection) manager.getConnection(this);
    if ( conn == null )
        break;
    try {
        if (conn.sock_m == null) {
      // otherwise handled by markUsed isAlive call
      conn.input.available();
        }
    } catch (IOException ex) {
        // mark that connection as dead
        // unregisterConnection(conn);
        conn.close();
        continue;
    }
    if ( conn.markUsed() ) {
        return conn;
    }
      }
      // Negotiate the creation of a new connection:
      if ( manager.negotiateConnection(this) ) {
    conn = allocateConnection();
    if ( conn.markUsed() ) {
        return conn;
    } else {
        // Failed to establish a fresh connection !
        return null;
    }
      }
      // Wait for a connection to become available:
      try {
    long _stime = System.currentTimeMillis();
    manager.waitForConnection(this);
    long _etime = System.currentTimeMillis();
    // if we waited enough... (it avoid bumping _waits if multiple
    // notify() are sent when closing a bunch of connections
    if (_etime - _stime > 20000) {
        if (debug) {
      System.err.println("wait "+_waits+" diff is "+
             (_etime - _stime));
      System.err.println("ManagerState: "+manager);
        }
        _waits++;
    }
      } catch (InterruptedException ex) {
    // interrupted, probably a timeout -> fail
    return null;
      }
  }
  return null;
    }

    /**
     * Display this server into a String.
     * @return A String based representation of the server object.
     */

    public String toString() {
  return host+":"+port;
    }

    /**
     * A full round-trip has been run with the target server, update infos.
     * Each server instance maintains a set of informations to be reused
     * if needed when recontacting the server later. After a full round
     * trip has been performed with the server, it is time to update
     * the target server version number, and keeps-alive flag.
     * @param reply The first reply we got from this server.
     */

    protected synchronized void updateServerInfo(Reply reply) {
  if ( contacted )
      return;
  major     = reply.getMajorVersion();
  minor     = reply.getMinorVersion();
  keepalive = reply.keepsAlive();
  contacted = true;
  if ( debug )
      System.out.println("*** " + this
                               + " major=" + major
                               + ", minor=" + minor
                               + ", ka="+keepalive);
    }

    /**
     * HttpServer implementation - Initialize this server to its target.
     * @param manager The central HTTP manager.
     * @param state The manager's state for that server.
     * @param host The target server's host name.
     * @param port The target server's port number.
     * @param timeout The timeout for the connection handled by the server
     * @exception HttpException If the server host couldn't be resolved
     * to one or more IP addresses.
     */

    public void initialize(HttpManager manager
         , HttpServerState state
         , String host, int port, int timeout)
  throws HttpException
    {
  initialize(manager, state, host, port, timeout, conn_timeout);
    }

    /**
     * HttpServer implementation - Initialize this server to its target.
     * @param manager The central HTTP manager.
     * @param state The manager's state for that server.
     * @param host The target server's host name.
     * @param port The target server's port number.
     * @param timeout The timeout for the connection handled by the server
     * @param timeout The connection timeout in millisecond for the sockets
     * @exception HttpException If the server host couldn't be resolved
     * to one or more IP addresses.
     */

    public void initialize(HttpManager manager
         , HttpServerState state
         , String host, int port, int timeout
         , int conn_timeout)
  throws HttpException
    {
  this.manager      = manager;
  this.state        = state;
  this.host         = host;
  this.port         = port;
  this.timeout      = timeout;
  this.conn_timeout = conn_timeout;
  // Get this server IP addreses:
  this.lookupLimit = null;
  updateHostAddr();
  this.state.state = HttpServerState.OK;
    }

    // FIXME doc
    protected void notifyObserver(RequestObserver obs
          , Request request
          , int code) {
  RequestEvent evt = new RequestEvent(this, request, code);
  obs.notifyProgress(evt);
    }

    protected void notifyObserver(RequestObserver obs, RequestEvent evt) {
  obs.notifyProgress(evt);
    }

    /**
     * Is this request a two stage request.
     * @return A boolean, <strong>true</strong> if the request is two
     * stage, <strong>false</strong> otherwise.
     */

    protected boolean isTwoStage_10(Request request) {
  return request.hasOutputStream();
    }

    /**
     * Is this request a two stage request.
     * @return A boolean, <strong>true</strong> if the request is two
     * stage, <strong>false</strong> otherwise.
     */

    protected boolean isTwoStage_11(Request request) {
  boolean hasOutput = request.hasOutputStream();
  if (hasOutput) {
      if (request.getExpect() != null) // don't test yet the 100-Continue
    return true;
  }
  return false;
    }

    /**
     * Get the current mode of running request for that server.
     * This method check our knowledge of the target server, and deduce
     * the mode in which the given request should be run.
     * @return An integer code, indicating the mode in which the request should
     * be run:
     * <dl>
     * <dt>RQ_HTTP11<dl>The request should be run as an HTTP/1.1 request.
     * <dt>RQ_HTTP10<dl>The request should be run as an HTTP/1.0 request.
     * <dt>RQ_HTTP10_KA<dl>HTTP/1.0 with keep-alive support.
     * <dt>RQ_UNKNOWN</dl>This is the first request, we don't know yet.
     * </dl>
     */

    protected int getRequestMode(Request request) {
  // Fast check for the server (we don't support HTTP/0.9):
  if ((! contacted) || (major < 1))
      return RQ_UNKNOWN;
  // Is this a 1.0 or 1.1 request ?
  if ( minor < 1 ) {
      return keepalive ? RQ_HTTP10_KA : RQ_HTTP10;
  } else {
      // Check for the method now:
      return isTwoStage_11(request) ? RQ_HTTP11_TS : RQ_HTTP11;
  }
    }

    /**
     * Run a fully HTTP/1.1 compliant request.
     * This request has no body, so we can do whatever we want with it,
     * and retry as many time as we want.
     * @param conn The connection to run the request on.
     * @param request The request to run.
     * @return A Reply instance, if success; <strong>null</strong> if the
     * request should be retried.
     * @exception IOException If some IO error occured.
     * @exception MimeParserException If some MIME parsing error occured.
     */

    protected Reply http11_run(HttpBasicConnection conn, Request request)
  throws IOException, MimeParserException
    {
  if ( debug )
      System.out.println(conn+": runs[11] "+request.getURL());
  RequestObserver o  = request.getObserver();
  OutputStream os    = conn.getOutputStream();
  MimeParser   p     = conn.getParser();
  Reply        reply = null;
  boolean      needsChunk = false;

  if (request.hasOutputStream()) {
      if (request.getContentLength() < 0 ) {
    needsChunk = true;
    String tes[] = request.getTransferEncoding();
    if (tes == null) {
        // FIXME intern this
        request.addTransferEncoding("chunked");
    } else {
        boolean addIt = true;
        for (int i=0; !addIt && (i < tes.length); i++) {
      addIt = addIt && !tes[i].equals("chunked");
        }
        if (addIt) {
      // FIXME intern this
                        request.addTransferEncoding("chunked");
        } else {
      if (os instanceof ChunkedOutputStream) {
          needsChunk = false;
      }
        }
    }
      }  
  }

  try {
      request.emit(os, Request.EMIT_HEADERS);
      os.flush();
      if (o != null) {
    notifyObserver(o, new ConnectedEvent(this, request, os));
      }
      // We don't expect a "100-continue" so let's dump the body
      // If any...
      if ( request.hasOutputStream() ) {
    String       exp   = request.getExpect();
    if ((exp != null) && (exp.equalsIgnoreCase("100-continue"))) {
        if ( o != null) {
      // client is expecting a 100 continue let's fake one
      notifyObserver(o, new ContinueEvent(this, request));
        }
    }
    if (needsChunk) {
        DataOutputStream dos = new DataOutputStream(os);
        ChunkedOutputStream cos = new ChunkedOutputStream(dos);
        request.emit(cos, Request.EMIT_BODY);
        cos.flush();
        cos.close(false);
        request.emit(os,Request.EMIT_FOOTERS);
    } else {
        request.emit(os,Request.EMIT_BODY|Request.EMIT_FOOTERS);
    }
    os.flush();
      }
      reply = (Reply) p.parse(manager.isLenient());
      // "eat" the 100 replies FIXME it indicates an error in
      // the upstream server
      while ((reply.getStatus() / 100) == 1 ) {
    if (o != null) {
        notifyObserver(o, new ContinueEvent(this, request, reply));
    }
    reply = (Reply) p.parse(manager.isLenient());
      }
  } catch (MimeParserException ex) {
      return null;
  } catch (IOException ioex) {
      return null;
      // Ok, we should give it another chance:
  }
  if (reply != null) {
      conn.setCloseOnEOF(reply.hasConnection("close"));
  }
  return reply;
    }

    /**
     * Run a two stage HTTP/1.1 compliant request.
     * The neat thing about this sort of request is that as they support
     * <strong>100</strong> status codes, we <em>know</em> when we have
     * to retry them.
     * @param conn The connection to run the request on.
     * @param request The request to run.
     * @return A Reply instance, if success; <strong>null</strong> if the
     * request should be retried.
     * @exception IOException If some IO error occured.
     * @exception MimeParserException If some MIME parsing error occured.
     */

    protected Reply http11_ts_run(HttpBasicConnection conn, Request request)
  throws IOException, MimeParserException
    {
  if ( debug )
      System.out.println(conn+": runs[11ts] "+request.getURL());
  RequestObserver o  = request.getObserver();
  OutputStream os    = conn.getOutputStream();
  MimeParser   p     = conn.getParser();
  Reply        reply = null;
  boolean needsChunk = false;
 
  if (request.getContentLength() < 0 ) {
      needsChunk = true;
      String tes[] = request.getTransferEncoding();
      if (tes == null) {
    request.addTransferEncoding("chunked"); // FIXME intern this
      } else {
    boolean addIt = true;
    for (int i=0; !addIt && (i < tes.length); i++) {
        addIt = addIt && !tes[i].equals("chunked");
    }
    if (addIt) {
        // FIXME intern thi
        request.addTransferEncoding("chunked");
    } else {
        if (os instanceof ChunkedOutputStream) {
      needsChunk = false;
        }
    }
      }
  }
 
  try {
      request.emit(os, Request.EMIT_HEADERS);
      os.flush();
      if ( o != null )
    notifyObserver(o, new ConnectedEvent(this, request, os));
      reply = (Reply) p.parse(manager.isLenient());
     
      boolean bodySent = false;
      while ((reply.getStatus() / 100) == 1 ||
       reply.getStatus() == HTTP.EXPECTATION_FAILED) {
    if (reply.getStatus() == HTTP.EXPECTATION_FAILED)
        return reply; // FIXME observer?
    reply = null;
    // Notify the observer if any:
    if ( o != null ) {
        notifyObserver(o, new ContinueEvent(this, request, reply));
    }
    // Finish the request normally:
    if (!bodySent) {
        bodySent = true;
        if (needsChunk) {
      DataOutputStream dos = new DataOutputStream(os);
      ChunkedOutputStream cos = new ChunkedOutputStream(dos);
      request.emit(cos, Request.EMIT_BODY);
      cos.flush();
      cos.close(false);
      request.emit(os,Request.EMIT_FOOTERS);
        } else {
      request.emit(os,
             Request.EMIT_BODY|Request.EMIT_FOOTERS);
        }
        os.flush();
    }
    reply = (Reply) p.parse(manager.isLenient());
    // if we don't have any observer, eat the 100 continue!
    while ((reply.getStatus() / 100) == 1 ) {
        if (o != null) {
      notifyObserver(o,
                    new ContinueEvent(this, request, reply));
        }
        reply = (Reply) p.parse(manager.isLenient());
    }
      }
  } catch (MimeParserException ex) {
      return null;
  } catch (IOException ieox) {
      return null;
  }
  if ( reply != null ) {
      conn.setCloseOnEOF(reply.hasConnection("close"));
  }
  return reply;
    }

    /**
     * Run an HTTP/1.0 request that has support for keep alive.
     * This kind of request are the worst one with regard to the retry
     * strategy we can adopt.
     * @param conn The connection to run the request on.
     * @param request The request to run.
     * @return A Reply instance, if success; <strong>null</strong> if the
     * request should be retried.
     * @exception IOException If some IO error occured.
     * @exception MimeParserException If some MIME parsing error occured.
     */

    protected Reply http10_ka_run(HttpBasicConnection conn, Request request)
  throws IOException, MimeParserException
    {
  if ( debug )
      System.out.println(conn+": runs[10_ka] "+request.getURL());
  RequestObserver o  = request.getObserver();
  OutputStream os    = conn.getOutputStream();
  MimeParser   p     = conn.getParser();
  Reply        reply = null;
  String       exp   = request.getExpect();

  if ((exp != null) && (exp.equalsIgnoreCase("100-continue"))) {
      reply = request.makeReply(HTTP.EXPECTATION_FAILED);
      reply.setContent("100-continue is not supported by upstream "
           + "HTTP/1.0 Server");
      return reply;
  }
  if (request.getConnection() == null) {
      if ( request.hasProxy() ) {
    request.addProxyConnection("Keep-Alive");
      } else {
    request.addConnection("Keep-Alive");
      }
  }
  try {
      request.emit(os, Request.EMIT_HEADERS);
      os.flush();
  } catch (IOException ioex) {
      return null;
  }
  if ( o != null )
      notifyObserver(o, new ConnectedEvent(this, request, os));
  // If this is a two stage method, emit a fake continue event:
  if ( isTwoStage_10(request) ) {
      if ( o != null )
    notifyObserver(o, new ContinueEvent(this, request));
      request.emit(os, Request.EMIT_BODY|Request.EMIT_FOOTERS);
      os.flush();
  }
  try {
      reply = (Reply) p.parse(manager.isLenient());
      // if ever we switch to 1.1 in the meantime...
      while ((reply.getStatus() / 100) == 1 ) {
    if (o != null) {
        notifyObserver(o, new ContinueEvent(this, request, reply));
    }
    reply = null;
    reply = (Reply) p.parse(manager.isLenient());
      }
  } catch (IOException ex) {
      // at this point, we try to parse the reply if we have a body
      if (isTwoStage_10(request)) {
    try {
        reply = (Reply) p.parse(manager.isLenient());
    } catch (MimeParserException mex) {
        // a stale connection?
        return null;
    }
    try {
        request.getOutputStream().close();
    } catch (Exception cex) {};
    return reply;
      }
      return null;
  } catch (MimeParserException ex) {
  }
  if ( reply != null ) {
      conn.setCloseOnEOF(reply.hasConnection("close"));
  }
  return reply;
    }

    /**
     * Run a simple HTTP/1.0 request.
     * This server doesn't support keep-alive, we know the connection is
     * always fresh, we don't need to go into the retry buisness.
     * <p>That's <strong>cool</strong> !
     * @param conn The connection to run the request on.
     * @param request The request to run.
     * @return A Reply instance, if success; <strong>null</strong> if the
     * request should be retried.
     * @exception IOException If some IO error occured.
     * @exception MimeParserException If some MIME parsing error occured.
     */

    protected Reply http10_run(HttpBasicConnection conn, Request request)
  throws IOException, MimeParserException
    {
  if ( debug )
      System.out.println(conn+": runs[10] "+request.getURL());
  RequestObserver o  = request.getObserver();
  OutputStream os    = conn.getOutputStream();
  MimeParser   p     = conn.getParser();
  Reply        reply = null;
  String       exp   = request.getExpect();

  if ((exp != null) && (exp.equalsIgnoreCase("100-continue"))) {
      reply = request.makeReply(HTTP.EXPECTATION_FAILED);
      reply.setContent("100-continue is not supported by upstream "
           + "HTTP/1.0 Server");
      return reply;
  }
  // Emit the request headers:
  request.emit(os, Request.EMIT_HEADERS);
  os.flush();
  if ( o != null ) {
      notifyObserver(o, new ConnectedEvent(this, request, os));
      // If this is a two stage method, emit a fake continue event:
      if ( isTwoStage_10(request) )
    notifyObserver(o, new ContinueEvent(this, request));
  }
  // Then emit the body:
  try {
      request.emit(os, Request.EMIT_BODY|Request.EMIT_FOOTERS);
      os.flush();
  } catch (IOException ex) {
      // at this point, we try to parse the reply if we have a body
      if (isTwoStage_10(request)) {
    try {
        reply = (Reply) p.parse(manager.isLenient());
    } catch (MimeParserException mex) {
        // perhaps nothing so...
        throw ex;
    }
    if (reply != null) {
                    // close the request stream
        try {
      request.getOutputStream().close();
        } catch (Exception cex) {};
        return reply;
    }
      }
      // no reply throw the exception again
      throw ex;
  }
  // HTTP 100 status codes *are* forbidden here, unless the server
  // switches...
  try {
      reply = (Reply) p.parse(manager.isLenient());
      while ((reply.getStatus() / 100) == 1 ) {
    if (o != null) {
        notifyObserver(o, new ContinueEvent(this, request, reply));
    }
    reply = null;
    reply = (Reply) p.parse(manager.isLenient());
      }
  } catch (MimeParserException ex) {
      // be nice to lamers
  }
  conn.setCloseOnEOF(true);
  return reply;
    }

    /**
     * Run that request, we don't know what server we have at the other end.
     * We know the connection is fresh, we use the <code>http10_run</code>
     * and update the server infos by the end of processing.
     * @param conn The connection to run the request on.
     * @param request The request to run.
     * @return A Reply instance, if success; <strong>null</strong> if the
     * request should be retried.
     * @exception IOException If some IO error occured.
     * @exception MimeParserException If some MIME parsing error occured.
     */

    protected Reply http_unknown(HttpBasicConnection conn, Request request)
  throws IOException, MimeParserException
    {
  boolean eventfaked = false;
  if ( debug )
      System.out.println(conn+": runs[unknown] "+request.getURL());
  // Update the request with server specific information:
  if ( request.getConnection() == null ) {
      if ( request.hasProxy() ) {
    request.addProxyConnection("Keep-Alive");
      } else {
    request.addConnection("Keep-Alive");
      }
  }
  // FIXME neeeds to handle a first request using a chunked body
  // or without body. we need to chack if the server is 1.1
  // then issue the request if it's ok. (expect 100 continue with
  // a timeout?
  // Emit the request:
  RequestObserver o  = request.getObserver();
  OutputStream os    = conn.getOutputStream();
  MimeParser   p     = conn.getParser();
  Reply        reply = null;
  // Emit the request headers:
  request.emit(os, Request.EMIT_HEADERS);
  if ( o != null ) {
      notifyObserver(o, new ConnectedEvent(this, request, os));
      // If this is a two stage method, try to be nice:
      if ( isTwoStage_10(request) ) {
    // Always emit a fake continue event:
    eventfaked = true;
    notifyObserver(o, new ContinueEvent(this, request));
      }
  }
  // Then emit the body:
  try {
      request.emit(os, Request.EMIT_BODY|Request.EMIT_FOOTERS);
      os.flush();
  } catch (IOException ex) {
      // at this point, we try to parse the reply if we have a body
      if (isTwoStage_10(request)) {
    try {
        reply = (Reply) p.parse(manager.isLenient());
    } catch (MimeParserException mex) {
        // perhaps nothing so...
        throw ex;
    }
    if (reply != null) {
                    // close the request stream
        try {
      request.getOutputStream().close();
        } catch (Exception cex) {};
        return reply;
    }
      }
      // no reply throw the exception again
      throw ex;
  }
  try {
      reply = (Reply) p.parse(manager.isLenient());
      while ((reply.getStatus() / 100) == 1) {
    // If we already faked an event, skip that one:
    if ( eventfaked ) {
        eventfaked = false;
        continue;
    }
    // Notify the observer, if any:
    if ( o != null )
        notifyObserver(o, new ContinueEvent(this, request, reply));
    // Get next reply:
    reply = (Reply) p.parse(manager.isLenient());
      }
      // Now, we know about that server, update infos:
      if ( reply != null ) {
    updateServerInfo(reply);
      }
  } catch (MimeParserException ex) {
      // be nice to lamers
  }
  if ( reply != null) {
      if ((major == 1) && ((minor == 1) || keepalive)) {
    conn.setCloseOnEOF(false);
      } else {
    conn.setCloseOnEOF(true);
      }
  }
  return reply;
    }

    /**
     * Exceute given request on given conection, according to server.
     * @param conn The connection to use to run that request.
     * @param request The request to execute.
     * @exception IOException If some IO error occurs, or if the
     * request is interrupted.
     * @exception MimeParserException If taregt server doesn't conform to
     * the HTTP specification.
     */

    protected Reply doRequest(HttpBasicConnection conn, Request request)
  throws IOException, MimeParserException
    {
  // Check for an interrupted request ?
  if ( request.isInterrupted() )
      throw new IOException("Interrupted Request");
  // Mark that request as being processed by that connection:
  request.setState(STATE_CONNECTION, conn);
  // Process the request:
  switch(getRequestMode(request)) {
    case RQ_HTTP11:
        return http11_run(conn, request);
    case RQ_HTTP11_TS:
        return http11_ts_run(conn, request);
    case RQ_HTTP10_KA:
        return http10_ka_run(conn, request);
    case RQ_HTTP10:
        return http10_run(conn, request);
    case RQ_UNKNOWN:
        return http_unknown(conn, request);
    default:
        throw new RuntimeException("Implementation bug.");
  }
  // not reached
    }

    /**
     * Interrupt given request, that was launched by ourself.
     * @param request The request to interrupt.
     */

    protected void interruptRequest(Request request) {
  HttpBasicConnection c = null;
  c = (HttpBasicConnection) request.getState(STATE_CONNECTION);
  if ( c == null ) {
      // That request has terminated, or has not started
      ;
  } else {
      // Just kill the connection, to trigger some IO exception:
      c.markIdle(true);
  }
    }
    /**
     * Run the given request in synchronous mode.
     * @param request The request to run.
     * @return A Reply instance, containing the reply headers, and the
     * optional reply entity, to be read by the calling thread.
     * @exception HttpException If the request processing failed.
     */

    public Reply runRequest(Request request)
  throws HttpException
    {
  RequestObserver o = request.getObserver();
  if ( debug ) {
      System.out.println("Running request: "+request.getURL());
      request.dump(System.out);
  }
  // Acquire a connection to run the request:
  HttpBasicConnection conn   = null;
  Reply               reply  = null;
  Exception           lastex = null;
  try {
      // Notify that request is being queued:
      if ( o != null )
    notifyObserver(o, request, RequestEvent.EVT_QUEUED);
      // Allocate a connection and run the request:
      int maxretry = 1;
      if (!request.hasOutputStream()) {
    String method = request.getMethod();
    if (method.length() <= 6) {
        method = method.intern();
        if ((method == HTTP.GET) || (method == HTTP.HEAD) ||
      (method == HTTP.OPTIONS) || (method == HTTP.TRACE)) {
      maxretry = 3;
        }
    }
      }
      for (int i = 0 ; (reply == null) && (i < maxretry) ; i++) {
    // getConnection can return null, check documentation:
    if ((conn = getConnection()) == null) {
        continue;
    }
    // if we can't check a connection is OK, we need a hack
    // (ie if jdk < 1.4...
    if (maxretry == 1) {
        if (manager.keepbody) {
      if (request.hasOutputStream()) {
          BufferedInputStream bis;
          bis = new BufferedInputStream(
                                 request.getOutputStream());
          request.setOutputStream(bis);
          int cl = request.getContentLength();
          if (cl < 0 )
        cl = 65536;
          bis.mark(cl);
      }
        } else {
      // get only a fresh connection
      while (conn.cached) {
          conn.markIdle(true);
          conn = getConnection();
      }
        }
        // idempotent method, so we won't redo it
    }
    // Some connection ready, try using it:
    try {
        if ((reply = doRequest(conn, request)) == null) {
      // if a cached connection fails, it may be due
      // to a socket in bad shape...
      // workaround for the missing socket.isAlive() method
      // Get rid of that useless connection:
      if (conn.cached) {
//          conn.markIdle(true);
          --i;
          if (request.hasOutputStream() &&
         manager.keepbody) {
        request.getOutputStream().reset();
          }
      }
      conn.markIdle(true);
        }
    } catch (MimeParserException mex) {
        lastex = mex;
        conn.markIdle(true);
        throw (mex);
    } catch (Exception ioex) {
        lastex = ioex;
        if (debug)
      ioex.printStackTrace();
        // connection 1.0 failed so it was not a cached connection
        // restore the body if ther was one.
        try {
      if (request.hasOutputStream()) {
          request.getOutputStream().reset();
      }
        } catch (IOException mrex) {
      // mark/reset not supported on the stream
        } finally {
        // Get rid of that useless connection:
      conn.markIdle(true);
        }
    } catch (Throwable thro) {
        conn.markIdle(true);
    }
      }
      if (request.hasOutputStream()) {
    request.getOutputStream().close();
      }
      // Did the request really failed ?
      if ( reply == null ) {
    String msg = ("Unable to contact target server "
            + this
            + " after " + maxretry + " tries.");
    if ( o != null )
        notifyObserver(o, request, RequestEvent.EVT_UNREACHABLE);
    if (lastex != null) {
        throw new HttpException(request, null, lastex, msg);
    } else {
        throw new HttpException(request, msg);
    }
      }
      reply.matchesRequest(request);
      if ( o != null )
    notifyObserver(o, request, RequestEvent.EVT_REPLIED);
      // Can we try to reuse the connection ?
      if ( reply.keepsAlive() ) {
    reply.setStreamObserver(conn);
    // If the reply has no input stream, register the connection:
    if ( ! reply.hasInputStream() ) {
        conn.markIdle(false);
    }
      } else {
    conn.detach();
      }
      if ( debug ) {
    System.out.println("Request done !")
    reply.dump(System.out);
      }
  } catch (IOException ex) {
      ex.printStackTrace();
      if ( conn != null )
    conn.markIdle(true);
      if ( o != null )
    notifyObserver(o, request, RequestEvent.EVT_CLOSED);
      throw new HttpException(request, ex);
  } catch (MimeParserException ex) {
      ex.printStackTrace();
      if ( conn != null )
    conn.markIdle(true);
      if ( o != null )
    notifyObserver(o, request, RequestEvent.EVT_CLOSED);
      throw new HttpException(request, ex);
  }
  return reply;
    }

    HttpBasicServer() {
    }
}
TOP

Related Classes of org.w3c.www.protocol.http.HttpBasicServer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.