All Downloads are FREE. Search and download functionalities are using the official Maven repository.

xsul.http_client.HttpClientRequest Maven / Gradle / Ivy

/* -*- mode: Java; c-basic-offset: 4; indent-tabs-mode: nil; -*-  //------100-columns-wide------>|*/
/*
 * Copyright (c) 2002-2004 Extreme! Lab, Indiana University. All rights reserved.
 *
 * This software is open source. See the bottom of this file for the license.
 *
 * $Id: HttpClientRequest.java,v 1.24 2007/07/10 03:04:49 aslom Exp $
 */

package xsul.http_client;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.SocketException;
import java.util.Observable;
import java.util.Observer;
import xsul.MLogger;
import xsul.http_common.HttpConstants;
import xsul.http_common.NotifyCloseOutputStream;
import xsul.util.XsulUtil;

/**
 * This class represents client-side HTTP request.
 * It is ooptimzied for small memory footprint.
 *
 * @version $Revision: 1.24 $
 * @author Aleksander Slominski
 */
public class HttpClientRequest {
    private static final MLogger httpOutTracing = MLogger.getLogger("trace.xsul.http.client.out");

    final static private String CRLF = "\r\n";

    // request line
    protected String method;
    protected String requestUri;
    protected String httpVersion;

    //predefined headers
    protected String connection;
    protected String contentType;
    protected String userAgent;
    //NOTE: header field names are case-insensitive: http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
    protected static final String[] RESERVED_HEADERS = {
        "connection", "content-length", "content-type", "host", "user-agent" };

    // user defined headers -- kept as array not hashtable to minimize memory usage
    protected int headersEnd;
    protected String[] headerName;
    protected String[] headerValue;

    // internal communication details
    //private Socket socket;
    private String hostValue;
    //private String host;
    //private int port;
    private ClientSocketConnection conn;
    private HttpClientConnectionManager mgr;
    private boolean closed;
    private HttpClientResponse resp;
    //private InputStream socketInputSteam;
    private OutputStream outputStream;
    private ByteArrayOutputStream baos = new ByteArrayOutputStream(8 * 1024);
    private boolean requestReuse = false;

    public HttpClientRequest(ClientSocketConnection conn,
                             //ClientSocketFactory socketFactory,
                             HttpClientConnectionManager mgr)
        throws HttpClientException
    {
        //this.socket = socket;

        //System.out.println(getClass()+" "+conn.getSocket());

        this.conn = conn;
        this.mgr = mgr;
        //        this.host = host;
        //        this.port = port;
        if(conn.getPort() != 80) { //TODO handle SSL!
            hostValue = conn.getHost() + ":"+ conn.getPort();
        } else {
            hostValue = conn.getHost();
        }
        this.outputStream = conn.getOutputStream();
        //this.socketInputSteam = socketInputSteam;
        this.resp = new HttpClientResponse(conn, this);

    }

    public void setRequestLine(String method, String requestUri, String httpVersion)
        throws HttpClientException
    {
        if(closed) {
            throw new HttpClientException("internal error: request was alredy closed");
        }

        // 5.1.1 Method http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.1
        if(method == null || method.length() == 0) {
            throw new IllegalArgumentException("HTTP method be non empty");
        }
        this.method = method;
        // 5.1.2 Request-URI http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2
        if(requestUri == null || requestUri.length() == 0) {
            throw new IllegalArgumentException("request URI must be non empty path");
        }
        this.requestUri = requestUri;
        // 3.1 HTTP Version http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.1
        if(!"HTTP/1.0".equals(httpVersion) && !"HTTP/1.1".equals(httpVersion)) {
            throw new IllegalArgumentException("HTTP version must be 1.0 or 1.1 not "+httpVersion);
        }
        //TODO: make httpVersion to use by default 1.1 ?!
        this.httpVersion = httpVersion;
    }

    // Connection http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.10
    // 19.6.1.1 Changes to Simplify Multi-homed Web Servers and Conserve IP
    // 19.6.2 Compatibility with HTTP/1.0 Persistent Connections
    //   http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.6.1
    public void setConnection(String connection) throws HttpClientException {
        if(closed) {
            throw new HttpClientException("internal error: request was alredy closed");
        }
        this.connection = connection.toLowerCase();
        if("close".equals(connection)) {
            requestReuse = false;
        } else if ("keep-alive".equals(connection)) {
            requestReuse = true;
        } else {
            throw new HttpClientException("unsupported connection type");
        }
    }

    public void setContentType(String contentType) throws HttpClientException {
        if(closed) {
            throw new HttpClientException("internal error: request was alredy closed");
        }
        this.contentType = contentType;
    }

    // 14.43 User-Agent http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
    public void setUserAgent(String userAgent) throws HttpClientException {
        if(closed) {
            throw new HttpClientException("internal error: request was alredy closed");
        }
        this.userAgent = userAgent;
    }

    public void ensureHeadersCapacity(int capacity) throws HttpClientException {
        if(closed) {
            throw new HttpClientException("internal error: request was alredy closed");
        }
        if(headerName == null || headerName.length < capacity) {
            String[] newHeaderName = new String[capacity];
            String[] newHeaderValue = new String[capacity];
            // now copy exisiting headers
            if(headersEnd > 0) {
                assert headerName != null && headerName.length > 0;
                System.arraycopy(headerName, 0, newHeaderName, 0, headersEnd);
                System.arraycopy(headerValue, 0, newHeaderValue, 0, headersEnd);
            }
            headerName = newHeaderName;
            headerValue = newHeaderValue;
        }
    }

    public void setHeader(String headerName, String headerValue) throws HttpClientException {
        if(closed) {
            throw new HttpClientException("internal error: request was alredy closed");
        }
        //TODO: check that not overwrites any reserved header ...
        String name = headerName.toLowerCase().trim().intern();
        for (int i = 0; i < RESERVED_HEADERS.length; i++) {
            if(name == RESERVED_HEADERS[i]) {

            }
        }
        //TODO: replace header value if header already exsit and not just append ...
        if(headerName == null) throw new IllegalArgumentException();
        if(headerValue == null) throw new IllegalArgumentException();
        ensureHeadersCapacity(headersEnd + 1);
        this.headerName[headersEnd] = headerName;
        this.headerValue[headersEnd] = headerValue;
        headersEnd++;
    }


    public HttpClientResponse sendHeaders() throws HttpClientException {
        if(closed) {
            throw new HttpClientException("internal error: request was already closed");
        }
        return resp;
    }

    private void writeHeaders(int contentLength) throws HttpClientException {
        if(method == null) throw new HttpClientException("request method must be set");
        if(requestUri == null) throw new HttpClientException("request URI must be set");
        if(httpVersion == null) throw new HttpClientException("request HTTP version must be set");
        StringBuffer headerbuf = new StringBuffer(1024); //to minimize buffer copy
        headerbuf.append(method).append(' ')
            .append(requestUri).append(' ')
            .append(httpVersion).append(CRLF);

        //.append(httpProxyHost == null ? path : location)
        //.append(" HTTP/").append(HTTP_VERSION).append("\r\n")

        headerbuf.append("Host: ").append(hostValue).append(CRLF);
        if(userAgent != null) {
            headerbuf.append("User-Agent: ").append(userAgent).append(CRLF);
        }

        if(contentType != null) { //it can benull for GET ...
            headerbuf.append("Content-Type: ").append(contentType).append(CRLF);
        }

        if(contentLength > 0) {
            headerbuf.append("Content-Length: ").append(contentLength).append("\r\n");
        }

        // write user headers
        for (int i = 0; i < headersEnd; i++)
        {
            headerbuf.append(headerName[i]).append(": ").append(headerValue[i]).append(CRLF);
        }

        //write connection close
        if(connection != null) {
            headerbuf.append("Connection: ").append(connection).append("\r\n");
        }

        headerbuf.append(CRLF);

        //it is fine - we need octets so we are fine with "iso-8859-1" too
        final String ENC = HttpConstants.ISO88591_CHARSET;
        byte[] b;
        try {
            b = headerbuf.toString().getBytes(ENC);
        } catch (UnsupportedEncodingException e) {
            throw new HttpClientException("could nto serialize headers to "+ENC, e);
        }
        //TODO: is there better way then use StringBuffer -> maybe byte array?
        try {
            if(httpOutTracing.isFinestEnabled()) {
                String headers = new String(b, HttpConstants.ISO88591_CHARSET);
                String printableHeaders = XsulUtil.printable(headers, false);
                httpOutTracing.finest("TRACE: sending request headers:"
                                          +"---\n"
                                          +printableHeaders
                                          +"---\n");
            }

            // send headers
            outputStream.write(b);
            outputStream.flush();

        } catch (IOException e) {
            throw new HttpClientException("could not send HTTP request headers", e);
        }
    }

    public OutputStream getBodyOutputStream() throws HttpClientException
    {
        if(closed) {
            throw new HttpClientException("request was alredy closed");
        }
        return new NotifyCloseOutputStream( baos, new Observer() {
                    public void update(Observable o, Object arg) {
                        closeRequest();
                    }
                });
    }


    //private HttpClientResponse closeRequest() throws HttpClientException
    private void closeRequest() throws HttpClientException
    {
//        if(closed) {
//            throw new HttpClientException("internal error: request was alredy closed");
//        }
//        closed = true; //TODO: add checks for closed flag to ALL methods
        byte[] body = baos.toByteArray();
        int contentLength = body.length;
        writeHttpHeaderAndBody(contentLength, body);
//        writeHeaders(contentLength);
//        try {
//            if(httpOutTracing.isFinestEnabled()) {
//                String bodyAsString = new String(body, HttpConstants.ISO88591_CHARSET);
//                httpOutTracing.finest("TRACE: sending request body:"
//                                          +"---\n"
//                                          +XsulUtil.printable(bodyAsString, false)
//                                          +"---\n");
//            }
//            outputStream.write(body);
//            try {
//                outputStream.flush();
//            } catch(SocketException sox)  {
////                if(sox.getMessage().indexOf("Broken pipe") != -1) {
////                    //OK to ignore?
////                    //Caused by: java.net.SocketException: Broken pipe
////                    //        at java.net.SocketOutputStream.socketWrite0(Native Method)
////                    //        at java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:92)
////                    //        at java.net.SocketOutputStream.write(SocketOutputStream.java:136)
////                    //        at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:65)
////                    //        at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:123)
////                    //        at xsul.http_client.HttpClientRequest.closeRequest(HttpClientRequest.java:290)
////
////                } else {
//                    throw sox;
////                }
//            }
//            //socketOutputSteam.close(); //would KILL KeepAlive :(
//        } catch (IOException e) {
//            throw new HttpClientException("could not send HTTP request body", e);
//        }
    }

    public void writeHttpHeaderAndBody(int contentLength, byte[] body){
        writeHeaders(contentLength);
        if(closed) {
            throw new HttpClientException("internal error: request was alredy closed");
        }
        closed = true; //TODO: add checks for closed flag to ALL methods
        try {
            if(httpOutTracing.isFinestEnabled()) {
                String bodyAsString = new String(body, HttpConstants.ISO88591_CHARSET);
                httpOutTracing.finest("TRACE: sending request body:"
                                          +"---\n"
                                          +XsulUtil.printable(bodyAsString, false)
                                          +"---\n");
            }
            outputStream.write(body);
            try {
                outputStream.flush();
            } catch(SocketException sox)  {
//                if(sox.getMessage().indexOf("Broken pipe") != -1) {
//                    //OK to ignore?
//                    //Caused by: java.net.SocketException: Broken pipe
//                    //        at java.net.SocketOutputStream.socketWrite0(Native Method)
//                    //        at java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:92)
//                    //        at java.net.SocketOutputStream.write(SocketOutputStream.java:136)
//                    //        at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:65)
//                    //        at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:123)
//                    //        at xsul.http_client.HttpClientRequest.closeRequest(HttpClientRequest.java:290)
//
//                } else {
                    throw sox;
//                }
            }
            //socketOutputSteam.close(); //would KILL KeepAlive :(
        } catch (IOException e) {
            throw new HttpClientException("could not send HTTP request body", e);
        }
    }

    /** this should be called only be corespondig response object spawned in this request */
    void responseFinishedAndCanBeReused(boolean reuseConection) throws HttpClientException {
        if(!closed) {
            throw new HttpClientException("request must be closed before response is closed");
        }
        if(requestReuse && reuseConection) {
            mgr.notifyConnectionForReuse(conn);
        } else {
            conn.close();
        }
    }
}


/*
 * Indiana University Extreme! Lab Software License, Version 1.2
 *
 * Copyright (c) 2002-2004 The Trustees of Indiana University.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * 1) All redistributions of source code must retain the above
 *    copyright notice, the list of authors in the original source
 *    code, this list of conditions and the disclaimer listed in this
 *    license;
 *
 * 2) All redistributions in binary form must reproduce the above
 *    copyright notice, this list of conditions and the disclaimer
 *    listed in this license in the documentation and/or other
 *    materials provided with the distribution;
 *
 * 3) Any documentation included with all redistributions must include
 *    the following acknowledgement:
 *
 *      "This product includes software developed by the Indiana
 *      University Extreme! Lab.  For further information please visit
 *      http://www.extreme.indiana.edu/"
 *
 *    Alternatively, this acknowledgment may appear in the software
 *    itself, and wherever such third-party acknowledgments normally
 *    appear.
 *
 * 4) The name "Indiana University" or "Indiana University
 *    Extreme! Lab" shall not be used to endorse or promote
 *    products derived from this software without prior written
 *    permission from Indiana University.  For written permission,
 *    please contact http://www.extreme.indiana.edu/.
 *
 * 5) Products derived from this software may not use "Indiana
 *    University" name nor may "Indiana University" appear in their name,
 *    without prior written permission of the Indiana University.
 *
 * Indiana University provides no reassurances that the source code
 * provided does not infringe the patent or any other intellectual
 * property rights of any other entity.  Indiana University disclaims any
 * liability to any recipient for claims brought by any other entity
 * based on infringement of intellectual property rights or otherwise.
 *
 * LICENSEE UNDERSTANDS THAT SOFTWARE IS PROVIDED "AS IS" FOR WHICH
 * NO WARRANTIES AS TO CAPABILITIES OR ACCURACY ARE MADE. INDIANA
 * UNIVERSITY GIVES NO WARRANTIES AND MAKES NO REPRESENTATION THAT
 * SOFTWARE IS FREE OF INFRINGEMENT OF THIRD PARTY PATENT, COPYRIGHT, OR
 * OTHER PROPRIETARY RIGHTS.  INDIANA UNIVERSITY MAKES NO WARRANTIES THAT
 * SOFTWARE IS FREE FROM "BUGS", "VIRUSES", "TROJAN HORSES", "TRAP
 * DOORS", "WORMS", OR OTHER HARMFUL CODE.  LICENSEE ASSUMES THE ENTIRE
 * RISK AS TO THE PERFORMANCE OF SOFTWARE AND/OR ASSOCIATED MATERIALS,
 * AND TO THE PERFORMANCE AND VALIDITY OF INFORMATION GENERATED USING
 * SOFTWARE.
 */












© 2015 - 2025 Weber Informatics LLC | Privacy Policy