openrat-java-client

Unnamed repository; edit this file 'description' to name the repository.
git clone http://git.code.weiherhei.de/openrat-java-client.git
Log | Files | Refs

HttpClient.java (9965B)


      1 /*
      2 OpenRat Java-Client
      3 Copyright (C) 2009 Jan Dankert
      4  
      5 This library is free software; you can redistribute it and/or
      6 modify it under the terms of the GNU Library General Public
      7 License as published by the Free Software Foundation; either
      8 version 2 of the License, or (at your option) any later version.
      9 
     10 This library is distributed in the hope that it will be useful,
     11 but WITHOUT ANY WARRANTY; without even the implied warranty of
     12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     13 Library General Public License for more details.
     14 
     15 You should have received a copy of the GNU Library General Public
     16 License along with this library; if not, write to the
     17 Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
     18 Boston, MA  02110-1301, USA.
     19 
     20  */
     21 package de.openrat.client.util;
     22 
     23 import java.io.BufferedReader;
     24 import java.io.IOException;
     25 import java.io.InputStreamReader;
     26 import java.io.PrintWriter;
     27 import java.net.ConnectException;
     28 import java.net.InetSocketAddress;
     29 import java.net.Socket;
     30 import java.net.SocketAddress;
     31 
     32 import javax.net.SocketFactory;
     33 import javax.net.ssl.SSLSocketFactory;
     34 import javax.xml.bind.DatatypeConverter;
     35 
     36 import de.openrat.client.util.HttpRequest.HttpMethod;
     37 
     38 /**
     39  * HTTP-Client.
     40  * 
     41  * @author Jan Dankert
     42  */
     43 public class HttpClient
     44 {
     45 	/**
     46 	 * at the moment we are only supporting HTTP/1.0, because for HTTP/1.1 we
     47 	 * have to implement transfer chunked encoding, gzip-encoding, persistent
     48 	 * connections and much more. that is non-trivial. but HTTP/1.0 does the job
     49 	 * for us.
     50 	 */
     51 	private static final String HTTP_VERSION = "HTTP/1.0";
     52 
     53 	private PrintWriter logWriter;
     54 
     55 	private CMSConnection connection;
     56 
     57 	private ParameterMap parameter = new ParameterMap();
     58 
     59 	public HttpClient(CMSConnection connection)
     60 	{
     61 
     62 		super();
     63 		this.connection = connection;
     64 		this.logWriter = connection.getLogWriter();
     65 	}
     66 
     67 	/**
     68 	 * Sends a HTTP request to the server and parses the response.
     69 	 * 
     70 	 * @return server response as a DOM tree
     71 	 * @throws IOException
     72 	 *             if server is unrechable or responds non-wellformed XML
     73 	 */
     74 	public HttpResponse execute(HttpRequest request) throws IOException
     75 	{
     76 		final Socket socket = this.createSocket();
     77 
     78 		try
     79 		{
     80 			String httpUrl = this.connection.getServerPath();
     81 
     82 			// Do NOT use the UI, always use the API.
     83 			httpUrl = addTrailingSlash(httpUrl) + "api/";
     84 
     85 			final PrintWriter socketWriter = new PrintWriter(socket.getOutputStream(), true);
     86 
     87 			if (connection.getProxyHostname() != null)
     88 				// See RFC 2616 Section 5.1.2 "Request-URI"
     89 				// "The absolute URI form is REQUIRED when the request is being made to a proxy"
     90 				httpUrl = "http://" + this.connection.getServerHost() + httpUrl;
     91 
     92 			String queryString = parameter.toQueryString();
     93 			if (HttpMethod.GET.equals(request.getMethod()))
     94 				httpUrl = httpUrl + "?" + queryString;
     95 
     96 			// using HTTP/1.0 as this is supported by all HTTP-servers and
     97 			// proxys.
     98 			// We have no need for HTTP/1.1 at the moment.
     99 			String httpCommandLine = request.getMethod().name() + " " + httpUrl + " " + HTTP_VERSION + "\n";
    100 
    101 			socketWriter.write(httpCommandLine);
    102 
    103 			HttpHeaderMap requestHeader = new HttpHeaderMap();
    104 
    105 			// Setting the HTTP Header
    106 			requestHeader.put("Host", this.connection.getServerHost());
    107 			requestHeader.put("Accept-Language", connection.getLocale().getLanguage());
    108 			requestHeader.put("User-Agent", "Mozilla/5.0; compatible (" + HttpClient.class.getName() + ")");
    109 
    110 			requestHeader.putAll(request.getRequestHeader());
    111 
    112 			// keep-alive is only for future use. for now we MUST close the connection after the call.
    113 			String connectionStatus = connection.isKeepAlive() ? "keep-alive" : "close";
    114 			requestHeader.put("Connection", connectionStatus);
    115 
    116 			if (this.connection.getProxyUser() != null)
    117 			{
    118 				final String userPass = DatatypeConverter.printBase64Binary((connection.getProxyUser() + ":" + connection
    119 						.getProxyPassword()).getBytes());
    120 				requestHeader.put("Proxy-Authorization", "Basic " + userPass);
    121 			}
    122 
    123 			String cookieHeader = connection.getCookieStore().getCookieRequestHeader();
    124 
    125 			if (cookieHeader.length() > 0)
    126 				requestHeader.put("Cookie", cookieHeader);
    127 
    128 			if (HttpMethod.POST.equals(request.getMethod()))
    129 			{
    130 				requestHeader.put("Content-Type", "application/x-www-form-urlencoded");
    131 				requestHeader.put("Content-Length", "" + queryString.length());
    132 
    133 			}
    134 
    135 			// write HTTP-request-headers to socket
    136 			socketWriter.write(requestHeader.toHttpHeaderString());
    137 			// empty line after HTTP headers
    138 			socketWriter.write("\n");
    139 
    140 			if (this.logWriter != null)
    141 			{
    142 				logWriter.println("--- HTTP-Request ---");
    143 				logWriter.println(httpCommandLine);
    144 				logWriter.println(requestHeader.toHttpHeaderString());
    145 			}
    146 
    147 			// POST-request have the payload in the body
    148 			if (HttpMethod.POST.equals(request.getMethod()))
    149 			{
    150 
    151 				if (this.logWriter != null)
    152 				{
    153 					logWriter.println("\n" + queryString);
    154 				}
    155 
    156 				socketWriter.write(queryString);
    157 			}
    158 
    159 			socketWriter.flush();
    160 
    161 			// now waiting for the answer...
    162 			final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
    163 
    164 			String readLine = bufferedReader.readLine();
    165 
    166 			if (readLine == null)
    167 			{
    168 				throw new CMSServerErrorException("Server response is empty");
    169 			}
    170 
    171 			final String httpServerResponse = readLine.trim();
    172 			final String httpStatusCode = httpServerResponse.substring(9, 12);
    173 			final String httpServerMessage = httpServerResponse.substring(13);
    174 
    175 			if (this.logWriter != null)
    176 			{
    177 				logWriter.println("--- HTTP-Response ---");
    178 				logWriter.println(httpServerResponse);
    179 			}
    180 
    181 			HttpHeaderMap responseHeader = new HttpHeaderMap();
    182 			// Analyze the HTTP response headers
    183 			while (true)
    184 			{
    185 				String responseHeaderString = bufferedReader.readLine().trim();
    186 
    187 				if (responseHeaderString.equals(""))
    188 					break;
    189 
    190 				int pos = responseHeaderString.indexOf(": ");
    191 				if (pos > 1)
    192 				{
    193 					String key = responseHeaderString.substring(0, pos);
    194 					String value = responseHeaderString.substring(pos + 2);
    195 					responseHeader.put(key, value);
    196 				}
    197 				else
    198 				{
    199 					throw new CMSServerErrorException("Unknown HTTP response header:" + responseHeaderString);
    200 				}
    201 
    202 			}
    203 
    204 			if (this.logWriter != null)
    205 				logWriter.println(responseHeader.toHttpHeaderString());
    206 
    207 			if (responseHeader.containsKey("Set-Cookie"))
    208 			{
    209 				this.parseRawCookie(responseHeader.get("Set-Cookie"));
    210 			}
    211 
    212 			StringBuffer responseString = new StringBuffer();
    213 			// while (bufferedReader.ready())
    214 			// {
    215 			// responseString.append(bufferedReader.readLine() + "\n");
    216 			// }
    217 
    218 			String buffer;
    219 			while ((buffer = bufferedReader.readLine()) != null)
    220 			{
    221 				responseString.append(buffer + "\n");
    222 			}
    223 
    224 			if (this.logWriter != null)
    225 			{
    226 				logWriter.println();
    227 				logWriter.println(responseString + "\n\n\n");
    228 				logWriter.flush();
    229 			}
    230 
    231 			final HttpResponse httpResponse = new HttpResponse();
    232 			httpResponse.setPayload(responseString.toString());
    233 			httpResponse.setHttpStatus(new HttpStatus(NumberUtils.toInt(httpStatusCode), httpServerMessage));
    234 
    235 			return httpResponse;
    236 		}
    237 		finally
    238 		{
    239 			// always close the socket because sockets are not endless resources
    240 			if (!connection.isKeepAlive())
    241 				try
    242 				{
    243 					socket.close();
    244 				}
    245 				catch (Exception e)
    246 				{
    247 					; // we have done our very best to close the socket
    248 				}
    249 		}
    250 	}
    251 
    252 	/**
    253 	 * Add trailing slash (if it does not exist).
    254 	 *
    255 	 * @param path
    256 	 * @return
    257 	 */
    258 	private String addTrailingSlash(String path) {
    259 		if (path.endsWith("/"))
    260 			return path;
    261 		else
    262 			return path + "/";
    263 	}
    264 
    265 	public Socket createSocket() throws IOException
    266 	{
    267 
    268 		if (connection.getSocket() != null && connection.isKeepAlive())
    269 		{
    270 			if (logWriter != null)
    271 			{
    272 				logWriter.println("Reusing socket: " + connection.getSocket().toString());
    273 			}
    274 
    275 			return connection.getSocket();
    276 		}
    277 
    278 		// When a client uses a proxy, it typically sends all requests to that
    279 		// proxy, instead
    280 		// of to the servers in the URLs. Requests to a proxy differ from normal
    281 		// requests in one
    282 		// way: in the first line, they use the complete URL of the resource
    283 		// being requested,
    284 		// instead of just the path.
    285 
    286 		final boolean useProxy = connection.getProxyHostname() != null;
    287 
    288 		SocketFactory socketFactory;
    289 		if (connection.isSecure())
    290 		{
    291 			socketFactory = SSLSocketFactory.getDefault();
    292 		}
    293 		else
    294 		{
    295 			socketFactory = SocketFactory.getDefault();
    296 		}
    297 
    298 		Socket socket = socketFactory.createSocket();
    299 
    300 		SocketAddress socketAddress;
    301 		if (useProxy)
    302 		{
    303 			socketAddress = new InetSocketAddress(connection.getProxyHostname(), connection.getProxyPort());
    304 		}
    305 		else
    306 		{
    307 			socketAddress = new InetSocketAddress(connection.getServerHost(), connection.getServerPort());
    308 		}
    309 
    310 		if (logWriter != null)
    311 		{
    312 			logWriter.println("Creating Socket: " + socketAddress.toString());
    313 		}
    314 
    315 		socket.setKeepAlive(connection.isKeepAlive());
    316 		socket.setReuseAddress(false);
    317 
    318 		try
    319 		{
    320 			socket.connect(socketAddress, connection.getTimeout());
    321 		}
    322 		catch (ConnectException e)
    323 		{
    324 			throw new CMSException("cannot connect to " + socketAddress.toString() + e.getMessage(), e);
    325 		}
    326 
    327 		if (connection.isKeepAlive())
    328 		{
    329 			connection.setSocket(socket);
    330 		}
    331 
    332 		return socket;
    333 	}
    334 
    335 	public void setLogWriter(PrintWriter logWriter)
    336 	{
    337 		this.logWriter = logWriter;
    338 	}
    339 
    340 	private void parseRawCookie(String rawCookie)
    341 	{
    342 
    343 		String[] rawCookieParams = rawCookie.split(";");
    344 		String[] rawCookieNameAndValue = rawCookieParams[0].split("=");
    345 
    346 		if (rawCookieNameAndValue.length != 2)
    347 		{
    348 			this.logWriter.println("Set-Cookie: " + rawCookie + " - Invalid cookie: missing name and value");
    349 		}
    350 
    351 		String cookieName = rawCookieNameAndValue[0].trim();
    352 		String cookieValue = rawCookieNameAndValue[1].trim();
    353 
    354 		connection.getCookieStore().put(cookieName, cookieValue);
    355 
    356 		// Ignoring all cookie attributes, because we are only storing the
    357 		// cookies for this session.
    358 	}
    359 
    360 	public ParameterMap getParameter()
    361 	{
    362 		return parameter;
    363 	}
    364 
    365 }