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 }