/** * Copyright 2010 JBoss Inc * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.eclipse.webdav.http.client; import java.io.*; import java.net.*; import java.util.Date; import java.util.Vector; import org.eclipse.webdav.client.Policy; import org.eclipse.webdav.internal.kernel.utils.Assert; /** * A connection to an HTTP/1.0 or HTTP/1.1 compatable server. * <p> * <b>Note:</b> This class/interface is part of an interim API that is still under * development and expected to change significantly before reaching stability. * It is being made available at this early stage to solicit feedback from pioneering * adopters on the understanding that any code that uses this API will almost * certainly be broken (repeatedly) as the API evolves. * </p> */ public class HttpConnection implements IStatusCodes { // The HTTP version to use (1.0 or 1.1). private double httpVersion = 1.1; // The request method. private String method = "GET"; //$NON-NLS-1$ // The URL of the requested resource. private URL resourceUrl = null; // The URL of the proxy server. private URL proxyServerUrl = null; // The request and response headers. protected Header requestHeader = new Header(); protected Header responseHeader = new Header(); protected Header internalHeader = new Header(); // The status code and status message of the response. private int statusCode; private String statusMessage; // The socket factory, socket, and socket settings. private ISocketFactory socketFactory = null; private Socket socket = null; private int receiveBufferSize = 0; private int sendBufferSize = 0; private int soLinger = -1; private int soTimeout = 0; private boolean tcpNoDelay = false; // The actual socket streams. protected InputStream socketIn = null; protected OutputStream socketOut = null; // The user's socket streams. private InputStream is = null; private OutputStream os = null; // A boolean indicating whether the connection should be closed after // the current request. private boolean closeConnection; // A boolean indicating whether the client wishes the connection to // remain open after the current request. private boolean persistent = true; // A boolean indicating whether the client wishes the request's // content to be sent chunked encoded. private boolean sendChunked = true; // A boolean indicating whether this connection is currently // connected to a server. private boolean connected = false; // A boolean indicating whether the current request has been sent. private boolean sentRequest = false; // This connection's timestamp. private Date timestamp = null; /** * A request or response header. */ public class Header { // This header's field names private Vector fieldNames = new Vector(5); // This header's field values private Vector fieldValues = new Vector(5); /** * Adds the specified header field to this header. * * @param fieldName the new header field's name * @param fieldValue the new header field's value */ public void addField(String fieldName, String fieldValue) { Assert.isNotNull(fieldName); Assert.isNotNull(fieldValue); fieldNames.addElement(fieldName); fieldValues.addElement(fieldValue); } /** * Removes all header fields from this header. */ public void clear() { fieldNames.removeAllElements(); fieldValues.removeAllElements(); } /** * Returns the field value of the header field at the given * position, or <code>null</code> if there is no such postition. * * @param position the position of a header field * @return the field value of the header field at the given * position */ public String getFieldValue(int position) { if (position < 0 || position >= fieldValues.size()) return null; return (String) fieldValues.elementAt(position); } /** * Returns the field value of the header field with the given * field name, or <code>null</code> if there is no such field * name. * * @param fieldName the name of a header field * @return the field value of the header field with the given * field name */ public String getFieldValue(String fieldName) { Assert.isNotNull(fieldName); return getFieldValue(fieldNames.indexOf(fieldName)); } /** * Returns the field name of the header field at the given * position, or <code>null</code> if there is no such position. * * @param position the position of a header field in this header * @return the field name of the header field at the given * position */ public String getFieldName(int position) { if (position < 0 || position >= fieldNames.size()) return null; return (String) fieldNames.elementAt(position); } /** * Returns the number of header fields in this header. * * @return the number of header fields in this header */ public int size() { return fieldNames.size(); } } /** * A limited input stream reads up to a given number of bytes from * this connection's underlying socket input stream. */ private class LimitedInputStream extends InputStream { /** * The number of bytes remaining in this stream. */ private int bytesRemaining; /** * Creates a new limited input stream that reads up to * <code>length</code> bytes of data from this connection's * underlying socket input stream. * * @param length the length of this input stream */ public LimitedInputStream(int length) { Assert.isTrue(length >= 0); bytesRemaining = length; } /** * @see InputStream#available() */ public int available() throws IOException { return Math.min(socketIn.available(), bytesRemaining); } /** * @see InputStream#close() */ public void close() throws IOException { while (skip(4096) > 0); } /** * @see InputStream#read() */ public int read() throws IOException { if (bytesRemaining <= 0) return -1; --bytesRemaining; return socketIn.read(); } /** * @see InputStream#read(byte[], int, int) */ public int read(byte[] buf, int offset, int length) throws IOException { if (bytesRemaining <= 0) return -1; int result = socketIn.read(buf, offset, Math.min(length, bytesRemaining)); if (result > 0) bytesRemaining -= result; return result; } /** * @see InputStream#skip(int) */ public long skip(int amount) throws IOException { if (bytesRemaining <= 0) return -1; long result = socketIn.skip(Math.min(amount, bytesRemaining)); if (result > 0) bytesRemaining -= result; return result; } } /** * A chunked input stream reads bytes, that have been chunked * transfer encoded, from this connection's underlying socket input * stream. */ private class ChunkedInputStream extends InputStream { /** * The number of bytes remaining in this stream's current chunk. */ private int bytesRemaining = -1; /** * A boolean indicating whether the end of this stream has been * reached. */ private boolean atEnd = false; /** * @see InputStream#available() */ public int available() throws IOException { return Math.min(socketIn.available(), bytesRemaining); } /** * @see InputStream#close() */ public void close() throws IOException { while (skip(4096) > 0); } /** * @see InputStream#read() */ public int read() throws IOException { if (!atEnd && bytesRemaining <= 0) readChunkSize(); if (atEnd) return -1; --bytesRemaining; return socketIn.read(); } /** * @see InputStream#read(byte[], int, int) */ public int read(byte[] buf, int offset, int length) throws IOException { if (!atEnd && bytesRemaining <= 0) readChunkSize(); if (atEnd) return -1; int result = socketIn.read(buf, offset, Math.min(length, bytesRemaining)); if (result > 0) bytesRemaining -= result; return result; } /** * Read the size of the next chunk of data. */ private void readChunkSize() throws IOException { if (bytesRemaining == 0) readln(); String size = readln(); int index = size.indexOf(";"); //$NON-NLS-1$ if (index >= 0) size = size.substring(0, index); try { bytesRemaining = Integer.parseInt(size.trim(), 16); } catch (NumberFormatException exception) { throw new IOException(exception.getMessage()); } if (bytesRemaining == 0) { atEnd = true; readHeader(responseHeader); } } /** * @see InputStream#skip(int) */ public long skip(int amount) throws IOException { if (!atEnd && bytesRemaining <= 0) readChunkSize(); if (atEnd) return -1; long result = socketIn.skip(Math.min(amount, bytesRemaining)); if (result > 0) bytesRemaining -= result; return result; } } /** * A limited output stream writes up to a given number of bytes to * this connection's underlying socket input stream. */ private class LimitedOutputStream extends OutputStream { // The maximum number of bytes that can be written to this // stream. private int limit; // A boolean indicating whether this stream is closed. private boolean closed = false; /** * Creates a new limited output stream that writes up to the * given limit of bytes to this connection's underlying socket * output stream. * * @param limit the maximum number of bytes that can be written * to this stream */ public LimitedOutputStream(int limit) { Assert.isTrue(limit >= 0); this.limit = limit; } /** * @see OutputStream#close() */ public void close() throws IOException { if (closed) return; try { flush(); if (limit > 0) throw new IOException(Policy.bind("exception.contentLengthUnderflow")); //$NON-NLS-1$ readServerResponse(); } finally { closed = true; } } /** * @see OutputStream#flush() */ public void flush() throws IOException { if (closed) throw new IOException(Policy.bind("exception.closed")); //$NON-NLS-1$ socketOut.flush(); } /** * @see OutputStream#write(int) */ public void write(int data) throws IOException { if (closed) throw new IOException(Policy.bind("exception.closed")); //$NON-NLS-1$ if (limit == 0) throw new IOException(Policy.bind("exception.contentLengthExceeded")); //$NON-NLS-1$ --limit; socketOut.write(data); } /** * @see OutputStream#write(byte[], int, int) */ public void write(byte[] buffer, int offset, int count) throws IOException { if (closed) throw new IOException(Policy.bind("exception.closed")); //$NON-NLS-1$ if (count > limit) throw new IOException(Policy.bind("exception.contentLengthExceeded")); //$NON-NLS-1$ limit -= count; socketOut.write(buffer, offset, count); } } /** * A chunked output stream writes bytes to this connection's * underlying socket output stream. The bytes are chunked transfer * encoded before they are written to the stream. */ private class ChunkedOutputStream extends OutputStream { // The maximum size of this output stream's buffer private static final int MAX_BUFFER_SIZE = 1024; // A buffer that holds the next chunk of data to be written to // this output stream. private ByteArrayOutputStream buffer = new ByteArrayOutputStream(MAX_BUFFER_SIZE); // A boolean indicating whether this output stream is closed. private boolean closed = false; /** * @see OutputStream#close() */ public void close() throws IOException { if (closed) return; try { sendBuffer(); output(socketOut, "0\r\n\r\n"); //$NON-NLS-1$ readServerResponse(); } finally { closed = true; } } /** * @see OutputStream#flush() */ public void flush() throws IOException { if (closed) throw new IOException("closed"); //$NON-NLS-1$ sendBuffer(); socketOut.flush(); } /** * Writes the content of the buffer to this stream. */ public void sendBuffer() throws IOException { int chunkSize = buffer.size(); if (chunkSize > 0) { output(socketOut, Integer.toHexString(chunkSize) + "\r\n"); //$NON-NLS-1$ buffer.writeTo(socketOut); buffer.reset(); output(socketOut, "\r\n"); //$NON-NLS-1$ } } /** * @see OutputStream#write(int) */ public void write(int data) throws IOException { if (closed) throw new IOException(Policy.bind("exception.closed")); //$NON-NLS-1$ buffer.write(data); if (buffer.size() >= MAX_BUFFER_SIZE) sendBuffer(); } /** * @see OutputStream#write(byte[], int, int) */ public void write(byte[] buf, int off, int len) throws IOException { if (closed) throw new IOException(Policy.bind("exception.closed")); //$NON-NLS-1$ int bufferSize = buffer.size(); if (bufferSize + len < MAX_BUFFER_SIZE) { buffer.write(buf, off, len); } else { output(socketOut, Integer.toHexString(bufferSize + len) + "\r\n"); //$NON-NLS-1$ buffer.writeTo(socketOut); buffer.reset(); socketOut.write(buf, off, len); output(socketOut, "\r\n"); //$NON-NLS-1$ } } } /** * A cached output stream writes bytes to a cache before sending the * data to the underlying socket output stream. This is done so the * content length can be determined. */ private class CachedOutputStream extends OutputStream { // The initial size of this output stream's cache private static final int INITIAL_CACHE_SIZE = 1024; // A cache that stores the bytes written to this output stream. private ByteArrayOutputStream cache = new ByteArrayOutputStream(INITIAL_CACHE_SIZE); // A boolean indicating whether this output stream is closed. private boolean closed = false; /** * @see OutputStream#close() */ public void close() throws IOException { if (closed) return; try { String contentLength = Integer.toString(cache.size()); internalHeader.addField("Content-Length", contentLength); //$NON-NLS-1$ sendRequest(); cache.writeTo(socketOut); readServerResponse(); } finally { closed = true; } } /** * @see OutputStream#flush() */ public void flush() throws IOException { if (closed) throw new IOException(Policy.bind("exception.closed")); //$NON-NLS-1$ } /** * @see OutputStream#write(int) */ public void write(int data) throws IOException { if (closed) throw new IOException(Policy.bind("exception.closed")); //$NON-NLS-1$ cache.write(data); } /** * @see OutputStream#write(byte[], int, int) */ public void write(byte[] buffer, int offset, int count) throws IOException { if (closed) throw new IOException(Policy.bind("exception.closed")); //$NON-NLS-1$ cache.write(buffer, offset, count); } } /** * Creates a new connection on the specified resource. * * @param resourceUrl the <code>URL</code> of a resource */ public HttpConnection(URL resourceUrl) { Assert.isNotNull(resourceUrl); this.resourceUrl = resourceUrl; } /** * Creates a new connection on the specified resource. This connection * communicates through the proxy at the given proxy server * <code>URL</code>. * * @param proxyServerUrl the <code>URL</code> of a proxy server * @param resourceUrl the <code>URL</code> of a resource */ public HttpConnection(URL proxyServerUrl, URL resourceUrl) { Assert.isNotNull(proxyServerUrl); Assert.isNotNull(resourceUrl); this.proxyServerUrl = proxyServerUrl; this.resourceUrl = resourceUrl; } /** * Clear the request header. */ public void clearRequestHeader() { endRequest(); requestHeader.clear(); } /** * Close this connection. * * @exception IOException if there is an I/O error closing the socket */ public void close() throws IOException { endRequest(); if (connected) { connected = false; socket.close(); } } private void connect() throws IOException { if (!connected) { String protocol; String host; int port; if (proxyServerUrl == null) { protocol = resourceUrl.getProtocol(); host = resourceUrl.getHost(); port = getPort(resourceUrl); } else { protocol = proxyServerUrl.getProtocol(); host = proxyServerUrl.getHost(); port = getPort(proxyServerUrl); } if (socketFactory == null) { socket = new Socket(host, port); } else { socket = socketFactory.createSocket(protocol, host, port); } if (receiveBufferSize > 0) { socket.setReceiveBufferSize(receiveBufferSize); } if (sendBufferSize > 0) { socket.setSendBufferSize(sendBufferSize); } socket.setSoLinger(soLinger >= 0, soLinger >= 0 ? soLinger : 0); socket.setSoTimeout(soTimeout); socket.setTcpNoDelay(tcpNoDelay); socketOut = new BufferedOutputStream(socket.getOutputStream()); socketIn = new BufferedInputStream(socket.getInputStream()); closeConnection = httpVersion == 1.0 || !persistent; connected = true; } } private void endRequest() { if (sentRequest) { boolean failed = false; if (os != null) { try { os.close(); } catch (IOException e) { failed = true; } } try { getInputStream().close(); } catch (IOException e) { failed = true; } sentRequest = false; if (closeConnection || failed) { try { connected = false; socket.close(); } catch (IOException e) { // ignore or log? } } } os = null; is = null; internalHeader.clear(); } /** * Returns the version of HTTP this connection uses for communication * with servers. HTTP/1.1 is used by default. * * @return the version of HTTP this connection uses for communication * with servers * @see #setHttpVersion(double) */ public double getHttpVersion() { return httpVersion; } /** * Returns this connection's <code>InputStream</code>. * * @return this connection's <code>InputStream</code> * @exception IOException if an I/O error occurs while sending the * request */ public InputStream getInputStream() throws IOException { if (is != null) return is; sendRequest(); String transferEncoding = responseHeader.getFieldValue("Transfer-Encoding"); //$NON-NLS-1$ String contentLength = responseHeader.getFieldValue("Content-Length"); //$NON-NLS-1$ if ("chunked".equalsIgnoreCase(transferEncoding)) { //$NON-NLS-1$ is = new ChunkedInputStream(); } else if (method.equals("HEAD") && statusCode == HTTP_OK) { //$NON-NLS-1$ is = new LimitedInputStream(0); } else if (contentLength != null) { try { is = new LimitedInputStream(Integer.parseInt(contentLength)); } catch (NumberFormatException e) { throw new IOException(e.getMessage()); } } else if ((statusCode >= 100 && statusCode < 200) || statusCode == HTTP_NO_CONTENT || statusCode == HTTP_NOT_MODIFIED) { is = new LimitedInputStream(0); } else { closeConnection = true; is = socketIn; } return is; } /** * Returns this connection's <code>OutputStream</code>. * * @return this connection's <code>OutputStream</code> * @exception IOException if an I/O error occurs while sending the * request */ public OutputStream getOutputStream() throws IOException { if (os != null) return os; String contentLength = requestHeader.getFieldValue("Content-Length"); //$NON-NLS-1$ if (sendChunked && httpVersion > 1.0) { os = new ChunkedOutputStream(); } else if (contentLength != null) { try { os = new LimitedOutputStream(Integer.parseInt(contentLength)); } catch (NumberFormatException e) { throw new IOException(Policy.bind("exception.malformedContentLength")); //$NON-NLS-1$ } } else { os = new CachedOutputStream(); return os; } sendRequest(); return os; } /** * Returns a boolean indicating whether this connection should remain * open after each request. * * @return a boolean indicating whether this connection should remain * open after each request * @see #setPersistent(boolean) */ public boolean getPersistent() { return persistent; } private int getPort(URL url) { String protocol = url.getProtocol(); int port = url.getPort(); if (port == -1) { if (protocol.equals("http")) //$NON-NLS-1$ return 80; if (protocol.equals("https")) //$NON-NLS-1$ return 443; return -1; } return port; } /** * Returns the <code>URL</code> of the proxy server this connection uses * to communicate with the origin server, or <code>null</code> if a proxy * server is not used. * * @return the <code>URL</code> of the proxy server this connection uses * to communicate with the origin server * @see #setProxyServerUrl(URL) */ public URL getProxyServerUrl() { return proxyServerUrl; } /** * @see Socket#getReceiveBufferSize() * @see #setReceiveBufferSize(int) */ public int getReceiveBufferSize() throws IOException { if (connected) return socket.getReceiveBufferSize(); return receiveBufferSize; } /** * Returns the request header value associated with the given field name, * or <code>null</code> if there is no such field name. * * @param fieldName the request header field name * @return the request header value associated with the given field name * @see #setRequestHeaderField(String, String) */ public String getRequestHeaderFieldValue(String fieldName) { Assert.isNotNull(fieldName); return requestHeader.getFieldValue(fieldName); } /** * Returns the request method. "GET" is used by default. * * @return the request method * @see #setRequestMethod(String) */ public String getRequestMethod() { return method; } /** * Returns the <code>URL</code> of this connection's resource. * * @return the <code>URL</code> of this connection's resource * @see #setResourceUrl(URL) */ public URL getResourceUrl() { return resourceUrl; } /** * Returns the response header field name at the given position, or * <code>null</code> if there is no field name at that position. * * @param position a position in the response header greater than or * equal to zero * @return the response header field name at the given postion * @exception IOException if an I/O error occurs while sending the * request * @see #getResponseHeaderFieldValue(int) * @see #getResponseHeaderFieldValue(String) */ public String getResponseHeaderFieldName(int position) throws IOException { Assert.isTrue(position >= 0); sendRequest(); return responseHeader.getFieldName(position); } /** * Returns the response header field value at the given position, or * <code>null</code> if there is no value at that position. * * @param position a position in the response header greater than or * equal to zero * @return the response header field value at the given postion * @exception IOException if an I/O error occurs while sending the * request * @see #getResponseHeaderFieldName(int) * @see #getResponseHeaderFieldValue(String) */ public String getResponseHeaderFieldValue(int position) throws IOException { Assert.isTrue(position >= 0); sendRequest(); return responseHeader.getFieldValue(position); } /** * Returns the response header field value that is associated with the * given field name, or <code>null</code> if there is no value associated * with that field name. * * @param fieldName the name of a response header field * @return the response header field value that is associated with the * given field name * @exception IOException if an I/O error occurs while sending the * request * @see #getResponseHeaderFieldValue(int) * @see #getResponseHeaderFieldName(int) */ public String getResponseHeaderFieldValue(String fieldName) throws IOException { Assert.isNotNull(fieldName); sendRequest(); return responseHeader.getFieldValue(fieldName); } /** * @see Socket#getSendBufferSize() * @see #setSendBufferSize(int) */ public int getSendBufferSize() throws IOException { if (connected) return socket.getSendBufferSize(); return sendBufferSize; } /** * Returns a boolean indicating whether the request's body should be sent * chunked encoded. * * @return a boolean indicating whether the request's body should be sent * chunked encoded * @see #setSendChunked(boolean) */ public boolean getSendChunked() { return sendChunked; } /** * @see Socket#getSoLinger() * @see #setSoLinger(boolean, int) */ public int getSoLinger() { return soLinger; } /** * @see Socket#getSoTimeout() * @see #setSoTimeout(int) */ public int getSoTimeout() { return soTimeout; } /** * Returns the status code of the server's response. * * @return the status code of the server's response * @exception IOException if an I/O error occurs while sending the * request * @see #getStatusMessage() */ public int getStatusCode() throws IOException { sendRequest(); return statusCode; } /** * Returns the status message of the server's response. * * @return the status message of the server's response * @exception IOException if an I/O error occurs while sending the * request * @see #getStatusCode() */ public String getStatusMessage() throws IOException { sendRequest(); return statusMessage; } /** * @see Socket#getTcpNoDelay() * @see #setTcpNoDelay(boolean) */ public boolean getTcpNoDelay() { return tcpNoDelay; } /** * Returns this connection's timestamp. * * @return this connection's timestamp * @see #setTimestamp(Date) */ public Date getTimestamp() { return timestamp; } protected void output(OutputStream stream, String output) throws IOException { stream.write(output.getBytes("UTF8")); //$NON-NLS-1$ } protected void readHeader(Header header) throws IOException { String line = null; while ((line = readln()).length() > 0) { int index = line.indexOf(":"); //$NON-NLS-1$ if (index < 0) throw new IOException(Policy.bind("exception.malformedHeaderField")); //$NON-NLS-1$ String fieldName = line.substring(0, index); String fieldValue = line.substring(index + 1).trim(); header.addField(fieldName, fieldValue); } String fieldValue = header.getFieldValue("Connection"); //$NON-NLS-1$ if (fieldValue != null && fieldValue.equalsIgnoreCase("close")) { //$NON-NLS-1$ closeConnection = true; } } protected String readln() throws IOException { boolean foundCR = false; StringBuffer result = new StringBuffer(); while (true) { int c = socketIn.read(); if (c < 0) { throw new IOException(Policy.bind("exception.unexpectedEndStream")); //$NON-NLS-1$ } if (foundCR) { if (c == '\n') break; result.append('\r'); } if (c == '\r') { foundCR = true; } else { result.append((char) c); } } return result.toString(); } protected void readServerResponse() throws IOException { socketOut.flush(); String statusLine = readln(); if (!statusLine.startsWith("HTTP/")) { //$NON-NLS-1$ throw new IOException(Policy.bind("exception.malformedStatusLine")); //$NON-NLS-1$ } int firstSpace = statusLine.indexOf(' ', 5); if (firstSpace == -1) { throw new IOException(Policy.bind("exception.malformedStatusLine")); //$NON-NLS-1$ } int secondSpace = statusLine.indexOf(' ', firstSpace + 1); if (secondSpace == -1) { throw new IOException(Policy.bind("exception.malformedStatusLine")); //$NON-NLS-1$ } double protocolVersion; try { protocolVersion = Double.parseDouble(statusLine.substring(5, firstSpace)); statusCode = Integer.parseInt(statusLine.substring(firstSpace + 1, secondSpace)); statusMessage = statusLine.substring(secondSpace + 1); } catch (NumberFormatException e) { throw new IOException(Policy.bind("exception.malformedStatusLine")); //$NON-NLS-1$ } if (protocolVersion == 1.0) { httpVersion = 1.0; closeConnection = true; } responseHeader.clear(); readHeader(responseHeader); } protected void sendRequest() throws IOException { if (sentRequest) return; connect(); if (requestHeader.getFieldValue("Host") == null) { //$NON-NLS-1$ internalHeader.addField("Host", //$NON-NLS-1$ resourceUrl.getHost() + (resourceUrl.getPort() == -1 ? "" : ":" + resourceUrl.getPort())); //$NON-NLS-1$ //$NON-NLS-2$ } if (httpVersion > 1.0 && !persistent && requestHeader.getFieldValue("Connection") == null) { //$NON-NLS-1$ internalHeader.addField("Connection", "close"); //$NON-NLS-1$ //$NON-NLS-2$ } if (os != null || "100-continue".equals(requestHeader.getFieldValue("Expect"))) { //$NON-NLS-1$ //$NON-NLS-2$ if (requestHeader.getFieldValue("Content-Type") == null) { //$NON-NLS-1$ internalHeader.addField("Content-Type", "application/x-www-form-urlencoded"); //$NON-NLS-1$ //$NON-NLS-2$ } if (httpVersion > 1.0 && sendChunked) { internalHeader.addField("Transfer-Encoding", "chunked"); //$NON-NLS-1$ //$NON-NLS-2$ } } output(socketOut, method); output(socketOut, " "); //$NON-NLS-1$ output(socketOut, proxyServerUrl == null ? resourceUrl.getFile() : resourceUrl.toString()); output(socketOut, " HTTP/1.1\r\n"); //$NON-NLS-1$ writeHeader(internalHeader); writeHeader(requestHeader); output(socketOut, "\r\n"); //$NON-NLS-1$ sentRequest = true; if (os == null) { readServerResponse(); } } /** * Sets the version of HTTP this connection uses for communication with * servers. HTTP/1.1 is used by default. * * @param version the version of HTTP this connection uses for * communication with servers * @see #getHttpVersion() */ public void setHttpVersion(double version) { Assert.isTrue(version == 1.0 || version == 1.1); endRequest(); httpVersion = version; } /** * Sets a boolean indicating whether this connection should remain open * after each request. * * @param close a boolean indicating whether this connection should * remain open after each request * @see #getPersistent() */ public void setPersistent(boolean close) { endRequest(); persistent = close; } /** * Sets the <code>URL</code> of the proxy server this connection uses to * communicate with the origin server. If <code>null</code> is given, no * proxy server is used. * * @param proxyServerUrl the <code>URL</code> of a proxy server * @see #getProxyServerUrl() */ public void setProxyServerUrl(URL proxyServerUrl) { endRequest(); if (proxyServerUrl == null && this.proxyServerUrl == null) return; boolean closeConnection = true; if (proxyServerUrl != null && this.proxyServerUrl != null) { URL oldProxyServerUrl = null; URL newProxyServerUrl = null; try { oldProxyServerUrl = new URL(this.proxyServerUrl.getProtocol(), this.proxyServerUrl.getHost(), this.proxyServerUrl.getPort(), "/"); //$NON-NLS-1$ newProxyServerUrl = new URL(proxyServerUrl.getProtocol(), proxyServerUrl.getHost(), proxyServerUrl.getPort(), "/"); //$NON-NLS-1$ } catch (MalformedURLException e) { // ignore or log? } if (oldProxyServerUrl.equals(newProxyServerUrl)) { closeConnection = false; } } if (closeConnection) { try { close(); } catch (IOException e) { // ignore or log? } } this.proxyServerUrl = proxyServerUrl; } /** * @see Socket#setReceiveBufferSize(int) * @see #getReceiveBufferSize() */ public void setReceiveBufferSize(int size) throws IOException { Assert.isTrue(size > 0); if (size != getReceiveBufferSize()) { receiveBufferSize = size; if (connected) { socket.setReceiveBufferSize(receiveBufferSize); } } } /** * Sets the request header value associated with the given field. * * @param fieldName the request header field * @param fieldValue the request header value * @see #getRequestHeaderFieldValue(String) */ public void setRequestHeaderField(String fieldName, String fieldValue) { Assert.isNotNull(fieldName); Assert.isNotNull(fieldValue); endRequest(); requestHeader.addField(fieldName, fieldValue); } /** * Sets the request method. "GET" is used by default. * * @param method the request method * @see #getRequestMethod() */ public void setRequestMethod(String method) { Assert.isNotNull(method); endRequest(); this.method = method; } /** * Sets the <code>URL</code> of this connection's resource. * * @param resourceUrl the <code>URL</code> of this connection's resource * @see #getResourceUrl() */ public void setResourceUrl(URL resourceUrl) { Assert.isNotNull(resourceUrl); endRequest(); URL oldOriginServerUrl = null; URL newOriginServerUrl = null; try { oldOriginServerUrl = new URL(this.resourceUrl.getProtocol(), this.resourceUrl.getHost(), this.resourceUrl.getPort(), "/"); //$NON-NLS-1$ newOriginServerUrl = new URL(resourceUrl.getProtocol(), resourceUrl.getHost(), resourceUrl.getPort(), "/"); //$NON-NLS-1$ } catch (MalformedURLException e) { // ignore? } if (!oldOriginServerUrl.equals(newOriginServerUrl)) { try { close(); } catch (IOException e) { // ignore? } } this.resourceUrl = resourceUrl; } /** * @see Socket#setSendBufferSize(int) * @see #getSendBufferSize() */ public void setSendBufferSize(int size) throws IOException { Assert.isTrue(size > 0); if (size != getSendBufferSize()) { sendBufferSize = size; if (connected) { socket.setSendBufferSize(sendBufferSize); } } } /** * Sets a boolean indicating whether the request's body should be sent * chunked encoded. * * @param chunked a boolean indicating whether the request's body should * be sent chunked encoded * @see #getSendChunked() */ public void setSendChunked(boolean chunked) { endRequest(); sendChunked = chunked; } /** * Sets the factory this connection uses to create sockets. If the given * socket factory is <code>null</code> the default socket is used. * * @param socketFactory the factory this connection uses to create * sockets */ public void setSocketFactory(ISocketFactory socketFactory) { endRequest(); if (socketFactory == this.socketFactory) return; try { close(); } catch (IOException e) { // ignore? } this.socketFactory = socketFactory; } /** * @see Socket#setSoLinger(boolean, int) * @see #getSoLinger() */ public void setSoLinger(boolean on, int linger) throws IOException { Assert.isTrue(linger >= 0); if (!on && soLinger != -1 || on && linger != soLinger) { soLinger = on ? linger : -1; if (connected) { socket.setSoLinger(on, linger); } } } /** * @see Socket#setSoTimeout(int) * @see #getSoTimeout() */ public void setSoTimeout(int timeout) throws IOException { Assert.isTrue(timeout >= 0); if (timeout != soTimeout) { soTimeout = timeout; if (connected) { socket.setSoTimeout(soTimeout); } } } /** * @see Socket#setTcpNoDelay(boolean) * @see #getTcpNoDelay() */ public void setTcpNoDelay(boolean on) throws IOException { if (on != tcpNoDelay) { tcpNoDelay = on; if (connected) { socket.setTcpNoDelay(tcpNoDelay); } } } /** * Sets this connection's timestamp. * * @param date this connection's timestamp * @see #getTimestamp() */ public void setTimestamp(Date date) { timestamp = date; } private void writeHeader(Header header) throws IOException { for (int i = 0; i < header.size(); ++i) { String fieldName = header.getFieldName(i); String fieldValue = header.getFieldValue(fieldName); output(socketOut, fieldName); output(socketOut, ": "); //$NON-NLS-1$ output(socketOut, fieldValue); output(socketOut, "\r\n"); //$NON-NLS-1$ } } }