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