HTTPRequest.java (15047B)
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.android.blog; 22 23 import java.io.BufferedReader; 24 import java.io.ByteArrayOutputStream; 25 import java.io.IOException; 26 import java.io.InputStream; 27 import java.io.OutputStream; 28 import java.io.Serializable; 29 import java.io.UnsupportedEncodingException; 30 import java.net.InetSocketAddress; 31 import java.net.Socket; 32 import java.net.SocketAddress; 33 import java.net.URLEncoder; 34 import java.util.ArrayList; 35 import java.util.HashMap; 36 import java.util.List; 37 import java.util.Locale; 38 import java.util.Map; 39 import java.util.Map.Entry; 40 41 /** 42 * API-Request to the OpenRat Content Management System. <br> 43 * <br> 44 * The call to the CMS server is done via a (non-SSL) HTTP connection.<br> 45 * <br> 46 * Before a call you are able to set some key/value-pairs as parameters. After 47 * calling the CMS a DOM-document is returned, which contains the server 48 * response.<br> 49 * Example <br> 50 * 51 * <pre> 52 * CMSRequest request = new CMSRequest("your.openrat.example.com"); 53 * //prints tracing information to stdout. 54 * request.trace = true; 55 * try 56 * { 57 * request.parameter.put("action", "index"); 58 * request.parameter.put("subaction", "showlogin"); // login page 59 * request.parameter.put("...", "..."); 60 * Document response = request.call(); 61 * // now traverse through the dom tree and get your information. 62 * } catch (IOException e) 63 * { 64 * // your error handling. 65 * } 66 * </pre> 67 * 68 * @author Jan Dankert 69 */ 70 public class HTTPRequest implements Serializable 71 { 72 73 private static final String CRLF = "\r\n"; 74 75 private Multipart multipart = new Multipart(); 76 77 // some constants... 78 private static final String CHARSET_UTF8 = "UTF-8"; 79 private static final String HTTP_GET = "GET"; 80 private static final String HTTP_POST = "POST"; 81 82 /** 83 * if <code>true</code>, Tracing-Output will be logged to stdout. Default: 84 * <code>false</code>. 85 */ 86 // this is public, for easier use. 87 public boolean trace = false; 88 89 /** 90 * HTTP-method, must be "GET" or "POST", default: "GET". 91 */ 92 private String method = HTTP_GET; 93 94 /** 95 * Parameter map. 96 */ 97 private Map<String, String> parameter = new HashMap<String, String>(); 98 private Map<String, String> requestHeader = new HashMap<String, String>(); 99 100 private String serverPath; 101 private String serverHost; 102 private int serverPort; 103 104 private String proxyHostname; 105 private int proxyPort; 106 private SocketAddress socketAddress; 107 108 private String cookieName; 109 private String cookieValue; 110 private String language; 111 private int timeout = 30000; 112 113 /** 114 * 115 */ 116 public HTTPRequest() 117 { 118 super(); 119 this.language = Locale.getDefault().getLanguage(); 120 } 121 122 public String getAcceptLanguage() 123 { 124 return language; 125 } 126 127 public void setAcceptLanguage(String language) 128 { 129 this.language = language; 130 } 131 132 /** 133 * Setting a HTTP-Cookie. 134 * 135 * @param name 136 * name 137 * @param value 138 * value 139 */ 140 public void setCookie(String name, String value) 141 { 142 143 this.cookieName = this.urlEncode(name); 144 this.cookieValue = this.urlEncode(value); 145 } 146 147 /** 148 * URL-Encoder. 149 * 150 * @param value 151 * @return url-encoded value 152 */ 153 private String urlEncode(String value) 154 { 155 156 try 157 { 158 return URLEncoder.encode(value, CHARSET_UTF8); 159 } catch (UnsupportedEncodingException e) 160 { 161 // maybe... this would be strange 162 throw new IllegalStateException(CHARSET_UTF8 163 + " ist not supported by this VM"); 164 } 165 } 166 167 /** 168 * Setting a HTTP-Proxy. 169 * 170 * @param host 171 * hostname 172 * @param port 173 * port 174 */ 175 public void setProxy(String host, int port) 176 { 177 178 this.proxyHostname = host; 179 this.proxyPort = port; 180 } 181 182 183 /** 184 * Timeout for Socket. 185 * @param timeout Timeout in milliseconds. 186 * @return old timeout 187 */ 188 public int setTimeout(int timeout) 189 { 190 int oldTimeout = this.timeout; 191 this.timeout = timeout; 192 193 return oldTimeout; 194 } 195 196 /** 197 * Set the HTTP Method. Default is "GET". 198 * 199 * @param method 200 * HTTP-method 201 */ 202 public void setMethod(String method) 203 { 204 205 if (!HTTP_GET.equalsIgnoreCase(method) 206 && !HTTP_POST.equalsIgnoreCase(method)) 207 throw new IllegalArgumentException("Method must be '" + HTTP_POST 208 + "' or '" + HTTP_GET + "'."); 209 210 this.method = method.toUpperCase(); 211 } 212 213 /** 214 * Clear parameter values. 215 */ 216 public void clearParameters() 217 { 218 219 parameter.clear(); 220 requestHeader.clear(); 221 multipart.parts.clear(); 222 } 223 224 /** 225 * Setting a parameter value. <strong>DO NOT url-encode your values</strong> 226 * as this is done automatically inside this method! 227 * 228 * @param paramName 229 * name 230 * @param paramValue 231 * value 232 */ 233 public void setParameter(String paramName, String paramValue) 234 { 235 236 if (paramName == null || paramValue == null || "" == paramName) 237 throw new IllegalArgumentException( 238 "parameter name and value must have values"); 239 240 parameter.put(paramName, paramValue); 241 } 242 243 /** 244 * 245 * Setting a parameter value. <strong>DO NOT url-encode your values</strong> 246 * as this is done automatically inside this method! 247 * 248 * @param paramName 249 * name 250 * @param paramValue 251 * value 252 */ 253 public void setHeader(String paramName, String paramValue) 254 { 255 256 if (paramName == null || paramValue == null || "" == paramName) 257 throw new IllegalArgumentException( 258 "parameter name and value must have values"); 259 260 requestHeader.put(paramName, paramValue); 261 } 262 263 /** 264 * Constructs a CMS-Request to the specified server.<br> 265 * Server-Path is "/", Server-Port is 80. 266 * 267 * @param host 268 * hostname 269 */ 270 public HTTPRequest(String host) 271 { 272 273 super(); 274 this.serverHost = host; 275 this.serverPath = "/"; 276 this.serverPort = 80; 277 } 278 279 /** 280 * Constructs a CMS-Request to the specified server/path.<br> 281 * Server-Port is 80. 282 * 283 * @param host 284 * hostname 285 * @param path 286 * path 287 */ 288 public HTTPRequest(String host, String path) 289 { 290 291 super(); 292 this.serverHost = host; 293 this.serverPath = path; 294 this.serverPort = 80; 295 } 296 297 /** 298 * Constructs a CMS-Request to the specified server/path/port. 299 * 300 * @param host 301 * hostname 302 * @param path 303 * path 304 * @param port 305 * port-number 306 */ 307 public HTTPRequest(String host, String path, int port) 308 { 309 310 super(); 311 this.serverHost = host; 312 this.serverPath = path; 313 this.serverPort = port; 314 } 315 316 /** 317 * Sends a request to the openrat-server and parses the response into a DOM 318 * tree document. 319 * 320 * @return server response as a DOM tree 321 * @throws IOException 322 * if server is unrechable or responds non-wellformed XML 323 */ 324 public byte[] performRequest() throws IOException 325 { 326 return performRequest(null); 327 } 328 329 /** 330 * Sends a request to the openrat-server and parses the response into a DOM 331 * tree document. 332 * 333 * @return server response as a DOM tree 334 * @throws IOException 335 * if server is unrechable or responds non-wellformed XML 336 */ 337 public byte[] performRequest(String body) throws IOException 338 { 339 340 final Socket socket = new Socket(); 341 342 try 343 { 344 345 final boolean useProxy = this.proxyHostname != null; 346 final boolean useCookie = this.cookieName != null; 347 348 if (serverPath == null) 349 this.serverPath = "/"; 350 if (!serverPath.startsWith("/")) 351 this.serverPath = "/" + this.serverPath; 352 353 // When a client uses a proxy, it typically sends all requests to 354 // that proxy, instead 355 // of to the servers in the URLs. Requests to a proxy differ from 356 // normal requests in one 357 // way: in the first line, they use the complete URL of the resource 358 // being requested, 359 // instead of just the path. 360 if (useProxy) 361 { 362 socketAddress = new InetSocketAddress(this.proxyHostname, 363 this.proxyPort); 364 } else 365 { 366 socketAddress = new InetSocketAddress(this.serverHost, 367 serverPort); 368 } 369 370 socket.setKeepAlive(false); 371 socket.setReuseAddress(false); 372 socket.setSoTimeout(timeout); 373 socket.connect(socketAddress, timeout); 374 375 final StringBuffer header = new StringBuffer(); 376 377 final StringBuffer parameterList = new StringBuffer(); 378 379 for (Entry<String, String> entry : this.parameter.entrySet()) 380 { 381 if (parameterList.length() > 0) 382 parameterList.append("&"); 383 parameterList.append(this.urlEncode(entry.getKey())); 384 parameterList.append("="); 385 parameterList.append(this.urlEncode(entry.getValue())); 386 } 387 388 String httpUrl = this.serverPath; 389 390 if (useProxy) 391 // See RFC 2616 Section 5.1.2 "Request-URI" 392 // "The absolute URI form is REQUIRED when the request is being made to a proxy" 393 httpUrl = "http://" + this.serverHost + httpUrl; 394 395 if (HTTP_GET.equals(this.method) 396 || (body != null || multipart.parts.size() > 0)) 397 httpUrl = httpUrl + "?" + parameterList; 398 399 // using HTTP/1.0 as this is supported by all HTTP-servers and 400 // proxys. 401 // We have no need for HTTP/1.1 at the moment. 402 header.append(this.method + " " + httpUrl + " HTTP/1.0" + CRLF); 403 404 // Setting the HTTP Header 405 Map<String, String> headers = new HashMap<String, String>(); 406 headers.put("Host", this.serverHost); 407 headers.put("User-Agent", 408 "Mozilla/5.0; compatible (OpenRat android-client)"); 409 headers.put("Accept", "application/json"); 410 headers.put("Accept-Language", language); 411 headers.put("Accept-Charset", "utf-8"); 412 headers.put("Connection", "close"); 413 if (useCookie) 414 headers.put("Cookie", cookieName + "=" + cookieValue); 415 416 if (HTTP_POST.equals(this.method)) 417 { 418 if (body == null && multipart.parts.size() == 0) 419 { 420 headers.put("Content-Type", 421 "application/x-www-form-urlencoded"); 422 headers.put("Content-Length", "" + parameterList.length()); 423 } else if (multipart.parts.size() > 0) 424 { 425 426 headers.put("Content-Type", multipart.getContentType()); 427 headers.put("Content-Length", "" 428 + multipart.getPayload().length); 429 430 } else 431 { 432 headers.put("Content-Type", "text/plain"); 433 434 } 435 } 436 437 headers.putAll(requestHeader); 438 for (String headerName : headers.keySet()) 439 { 440 header.append(headerName + ": " + headers.get(headerName) 441 + CRLF); 442 443 } 444 445 header.append(CRLF); 446 447 final OutputStream outputStream = socket 448 .getOutputStream(); 449 outputStream.write(header.toString().getBytes()); 450 451 if (HTTP_POST.equals(this.method)) 452 { 453 if (body == null && multipart.parts.size() == 0) 454 outputStream.write(parameterList.toString().getBytes()); 455 else if (multipart.parts.size() > 0) 456 outputStream.write(multipart.getPayload()); 457 else 458 outputStream.write(body.getBytes()); 459 } 460 461 if (this.trace) 462 System.out.println("--- request ---"); 463 if (this.trace) 464 System.out.println(header.toString()); 465 466 467 outputStream.flush(); 468 469 final InputStream inputStream = socket.getInputStream(); 470 // final int available = inputStream.available(); 471 472 final BufferedReader bufferedReader = new BufferedReader( 473 new MyStreamReader(inputStream),1); 474 475 final String httpResponse = bufferedReader.readLine().trim(); 476 final String httpRetCode = httpResponse.substring(9, 12); 477 478 if (this.trace) 479 System.out.println("--- response ---"); 480 if (this.trace) 481 System.out.println(httpResponse); 482 483 // Check if we got the status 200=OK. 484 if (!httpRetCode.equals("200")) 485 { 486 487 // non-200-status seems to be an error. 488 throw new IOException("No HTTP 200: Status=" + httpRetCode 489 + " (" + httpResponse + ")"); 490 } 491 492 while (true) 493 { 494 495 String responseHeader = bufferedReader.readLine().trim(); 496 497 if (responseHeader.equals("")) 498 break; 499 500 if (this.trace) 501 System.out.println(responseHeader); 502 } 503 //inputStreamReader.reset(); 504 //inputStream.reset(); 505 506 ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 507 508 int nRead; 509 byte[] data = new byte[1024]; 510 511 while ((nRead = inputStream.read(data, 0, data.length)) != -1) { 512 buffer.write(data, 0, nRead); 513 } 514 buffer.flush(); 515 516 byte[] response = buffer.toByteArray(); 517 518 if (this.trace) 519 System.out.println("--- response body ---"); 520 if (this.trace) 521 System.out.println(response + "\n\n\n"); 522 523 return response; 524 } finally 525 { 526 try 527 { 528 socket.close(); // Clean up the socket. 529 } catch (IOException e) 530 { 531 // We have done our very best. 532 } 533 } 534 535 } 536 537 public void setFile(String name, byte[] value, String filename, 538 String type, String encoding) 539 { 540 541 Part part = new Part(); 542 part.file = value; 543 part.filename = filename; 544 part.encoding = encoding; 545 part.name = name; 546 part.contentType = type; 547 this.multipart.parts.add(part); 548 } 549 550 public void setText(String name, String value) 551 { 552 553 Part part = new Part(); 554 part.name = name; 555 part.text = value; 556 part.contentType = "text/plain"; 557 this.multipart.parts.add(part); 558 } 559 560 private class Multipart implements Serializable 561 { 562 563 private static final String CRLF = "\r\n"; 564 private static final String BOUNDARY = "614BA262123F3B29656A745C5DD26"; 565 List<Part> parts = new ArrayList<Part>(); 566 567 public byte[] getPayload() throws IOException 568 { 569 HttpOutputStream body = new HttpOutputStream(); 570 571 for (Part part : parts) 572 { 573 body.append("--" + BOUNDARY + CRLF); 574 body.append("Content-Type: " + part.contentType + CRLF); 575 576 if (part.encoding != null) 577 body.append("Content-Transfer-Encoding: " + part.encoding 578 + CRLF); 579 580 body.append("Content-Disposition: form-data; name=\"" 581 + part.name 582 + "\"" 583 + (part.filename != null ? ("; filename=\"" 584 + part.filename + "\"") : "") + CRLF); 585 body.append(CRLF); 586 if (part.file.length > 0) 587 body.write(part.file); 588 else 589 body.append(part.text); 590 body.append(CRLF); 591 } 592 body.append("--" + BOUNDARY + "--"); 593 return body.toByteArray(); 594 } 595 596 public String getContentType() 597 { 598 return "multipart/form-data; boundary=" + Multipart.BOUNDARY; 599 } 600 } 601 602 private class Part implements Serializable 603 { 604 public byte[] file; 605 public String filename; 606 public String text; 607 public String name; 608 public String contentType; 609 public String encoding; 610 } 611 612 private class HttpOutputStream extends ByteArrayOutputStream 613 { 614 615 public void write(String s) throws IOException 616 { 617 super.write(s.getBytes()); 618 } 619 public void append(String s) throws IOException 620 { 621 super.write(s.getBytes()); 622 } 623 } 624 }