1 //****************************************************************************** 2 // 3 // File: HttpRequest.java 4 // Package: edu.rit.http 5 // Unit: Class edu.rit.http.HttpRequest 6 // 7 // This Java source file is copyright (C) 2006 by Alan Kaminsky. All rights 8 // reserved. For further information, contact the author, Alan Kaminsky, at 9 // ark@cs.rit.edu. 10 // 11 // This Java source file is part of the Parallel Java Library ("PJ"). PJ is free 12 // software; you can redistribute it and/or modify it under the terms of the GNU 13 // General Public License as published by the Free Software Foundation; either 14 // version 3 of the License, or (at your option) any later version. 15 // 16 // PJ is distributed in the hope that it will be useful, but WITHOUT ANY 17 // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 18 // A PARTICULAR PURPOSE. See the GNU General Public License for more details. 19 // 20 // Linking this library statically or dynamically with other modules is making a 21 // combined work based on this library. Thus, the terms and conditions of the GNU 22 // General Public License cover the whole combination. 23 // 24 // As a special exception, the copyright holders of this library give you 25 // permission to link this library with independent modules to produce an 26 // executable, regardless of the license terms of these independent modules, and 27 // to copy and distribute the resulting executable under terms of your choice, 28 // provided that you also meet, for each linked independent module, the terms 29 // and conditions of the license of that module. An independent module is a module 30 // which is not derived from or based on this library. If you modify this library, 31 // you may extend this exception to your version of the library, but you are not 32 // obligated to do so. If you do not wish to do so, delete this exception 33 // statement from your version. 34 // 35 // A copy of the GNU General Public License is provided in the file gpl.txt. You 36 // may also obtain a copy of the GNU General Public License on the World Wide 37 // Web at http://www.gnu.org/licenses/gpl.html. 38 // 39 //****************************************************************************** 40 package edu.rit.http; 41 42 import java.io.IOException; 43 import java.net.Socket; 44 import java.util.Collection; 45 import java.util.Collections; 46 import java.util.HashMap; 47 import java.util.Map; 48 import java.util.Scanner; 49 50 // For unit test main program 51 // import java.io.PrintWriter; 52 // import java.net.InetSocketAddress; 53 // import java.net.ServerSocket; 54 // import java.nio.charset.Charset; 55 /** 56 * Class HttpRequest encapsulates an HTTP request received from a web browser. 57 * <P> 58 * HTTP/1.0 and HTTP/1.1 requests are supported. The obsolete HTTP/0.9 requests 59 * are <I>not</I> supported. 60 * <P> 61 * This class provides methods for examining the request line and the headers. 62 * This class does <I>not</I> support reading the entity body if any. 63 * <P> 64 * To receive an HTTP request message: 65 * <OL TYPE=1> 66 * <LI> 67 * Call the <code>isValid()</code> method. 68 * <BR> 69 * <LI> 70 * If <code>isValid()</code> returns false, send an HTTP response message indicating 71 * the error. 72 * <BR> 73 * <LI> 74 * If <code>isValid()</code> returns true, call the other methods to examine the 75 * contents of the HTTP request message, and send an appropriate HTTP response 76 * message. 77 * </OL> 78 * 79 * @author Alan Kaminsky 80 * @version 29-Jul-2010 81 */ 82 public class HttpRequest { 83 84 // Exported constants. 85 /** 86 * The GET method string, <code>"GET"</code>. 87 */ 88 public static final String GET_METHOD = "GET"; 89 90 /** 91 * The HEAD method string, <code>"HEAD"</code>. 92 */ 93 public static final String HEAD_METHOD = "HEAD"; 94 95 /** 96 * The POST method string, <code>"POST"</code>. 97 */ 98 public static final String POST_METHOD = "POST"; 99 100 /** 101 * The HTTP/1.0 version string <code>"HTTP/1.0"</code>. 102 */ 103 public static final String HTTP_1_0_VERSION = "HTTP/1.0"; 104 105 /** 106 * The HTTP/1.1 version string, <code>"HTTP/1.1"</code>. 107 */ 108 public static final String HTTP_1_1_VERSION = "HTTP/1.1"; 109 110 // Hidden data members. 111 private Socket mySocket; 112 113 private String myMethod; 114 private String myUri; 115 private String myHttpVersion; 116 117 private Map<String, String> myHeaderMap 118 = new HashMap<String, String>(); 119 private Map<String, String> myUnmodifiableHeaderMap 120 = Collections.unmodifiableMap(myHeaderMap); 121 122 private boolean iamValid; 123 124 // Exported constructors. 125 /** 126 * Construct a new HTTP request. The request is read from the input stream 127 * of the given socket. 128 * 129 * @param theSocket Socket. 130 * @exception NullPointerException (unchecked exception) Thrown if 131 * <code>theSocket</code> is null. 132 */ 133 public HttpRequest(Socket theSocket) { 134 if (theSocket == null) { 135 throw new NullPointerException("HttpRequest(): theSocket is null"); 136 } 137 mySocket = theSocket; 138 } 139 140 // Exported operations. 141 /** 142 * Determine if this HTTP request is valid. If the data read from the input 143 * stream of the socket given to the constructor represents a valid HTTP 144 * request message, true is returned, otherwise false is returned. If an I/O 145 * exception is thrown while reading the input, this HTTP request is marked 146 * as invalid, but the I/O exception is not propagated to the caller. 147 * 148 * @return True if this HTTP request is valid, false otherwise. 149 */ 150 public boolean isValid() { 151 parse(); 152 return iamValid; 153 } 154 155 /** 156 * Obtain this HTTP request's method. 157 * 158 * @return Method string, e.g. <code>"GET"</code>, <code>"POST"</code>. 159 * @exception IllegalStateException (unchecked exception) Thrown if this 160 * HTTP request is invalid. 161 */ 162 public String getMethod() { 163 if (!isValid()) { 164 throw new IllegalStateException("HTTP request is invalid"); 165 } 166 return myMethod; 167 } 168 169 /** 170 * Obtain this HTTP request's URI. 171 * 172 * @return URI string. 173 * @exception IllegalStateException (unchecked exception) Thrown if this 174 * HTTP request is invalid. 175 */ 176 public String getUri() { 177 if (!isValid()) { 178 throw new IllegalStateException("HTTP request is invalid"); 179 } 180 return myUri; 181 } 182 183 /** 184 * Obtain this HTTP request's version. 185 * 186 * @return HTTP version string, e.g. <code>"HTTP/1.0"</code>, 187 * <code>"HTTP/1.1"</code>. 188 * @exception IllegalStateException (unchecked exception) Thrown if this 189 * HTTP request is invalid. 190 */ 191 public String getHttpVersion() { 192 if (!isValid()) { 193 throw new IllegalStateException("HTTP request is invalid"); 194 } 195 return myHttpVersion; 196 } 197 198 /** 199 * Obtain the value of the given header in this HTTP request. 200 * 201 * @param theHeaderName Header name. 202 * @return Header value, or null if there is no header for 203 * <code>theHeaderName</code>. 204 * @exception IllegalStateException (unchecked exception) Thrown if this 205 * HTTP request is invalid. 206 */ 207 public String getHeader(String theHeaderName) { 208 if (!isValid()) { 209 throw new IllegalStateException("HTTP request is invalid"); 210 } 211 return myHeaderMap.get(theHeaderName); 212 } 213 214 /** 215 * Obtain a collection of all the headers in this HTTP request. The returned 216 * object is an unmodifiable collection of zero or more map entries. Each 217 * map entry's key is the header name. Each map entry's value is the 218 * corresponding header value. 219 * 220 * @return Unmodifiable collection of header name-value mappings. 221 * @exception IllegalStateException (unchecked exception) Thrown if this 222 * HTTP request is invalid. 223 */ 224 public Collection<Map.Entry<String, String>> getHeaders() { 225 if (!isValid()) { 226 throw new IllegalStateException("HTTP request is invalid"); 227 } 228 return myUnmodifiableHeaderMap.entrySet(); 229 } 230 231 // Hidden operations. 232 /** 233 * Parse the input data read from this HTTP request's socket. 234 */ 235 private void parse() { 236 // Early return if already parsed. 237 if (myMethod != null) { 238 return; 239 } 240 241 // Assume the request is invalid. 242 iamValid = false; 243 myMethod = ""; 244 myUri = ""; 245 myHttpVersion = ""; 246 247 try { 248 // Set up to scan lines from the socket input stream. 249 Scanner scanner = new Scanner(mySocket.getInputStream()); 250 251 // Read the first line. If none, invalid. 252 if (!scanner.hasNextLine()) { 253 return; 254 } 255 String line = scanner.nextLine(); 256 257 // Parse the first line. 258 Scanner linescanner = new Scanner(line); 259 if (!linescanner.hasNext()) { 260 return; 261 } 262 String method = linescanner.next(); 263 if (!linescanner.hasNext()) { 264 return; 265 } 266 String uri = linescanner.next(); 267 if (!linescanner.hasNext()) { 268 return; 269 } 270 String httpVersion = linescanner.next(); 271 if (linescanner.hasNext()) { 272 return; 273 } 274 275 // Read remaining lines if any until an empty line. 276 String headerName = null; 277 StringBuilder headerValue = new StringBuilder(); 278 for (;;) { 279 if (!scanner.hasNextLine()) { 280 return; 281 } 282 line = scanner.nextLine(); 283 if (line.isEmpty()) { 284 break; 285 } 286 287 // Check whether line is starting or continuing a header. 288 if (Character.isWhitespace(line.charAt(0))) { 289 // Continuing previous header. 290 if (headerName == null) { 291 return; 292 } 293 headerValue.append(line); 294 } else { 295 // Starting new header. Record previous header if any. 296 if (headerName != null) { 297 myHeaderMap.put(headerName, headerValue.toString()); 298 headerName = null; 299 headerValue = new StringBuilder(); 300 } 301 302 // Parse header name and value. 303 int i = line.indexOf(':'); 304 if (i <= 0) { 305 return; 306 } 307 if (i >= line.length() - 1) { 308 return; 309 } 310 if (!Character.isWhitespace(line.charAt(i + 1))) { 311 return; 312 } 313 headerName = line.substring(0, i); 314 headerValue.append(line.substring(i + 2)); 315 } 316 } 317 318 // If we get here, all is well. Record final header if any. 319 if (headerName != null) { 320 myHeaderMap.put(headerName, headerValue.toString()); 321 } 322 323 // Record method, URI, and HTTP version. 324 myMethod = method; 325 myUri = uri; 326 myHttpVersion = httpVersion; 327 328 // Mark it valid. 329 iamValid = true; 330 } catch (IOException exc) { 331 // Leave it marked invalid. 332 } 333 } 334 335 // Unit test main program. 336 // /** 337 // * Unit test main program. The program listens for connections to 338 // * localhost:8080. The program reads each HTTP request from a web browser 339 // * and merely echoes the request data back to the browser. 340 // * <P> 341 // * Usage: java edu.rit.http.HttpRequest 342 // */ 343 // public static void main 344 // (String[] args) 345 // throws Exception 346 // { 347 // ServerSocket serversocket = new ServerSocket(); 348 // serversocket.bind (new InetSocketAddress ("localhost", 8080)); 349 // for (;;) 350 // { 351 // Socket socket = serversocket.accept(); 352 // HttpRequest request = new HttpRequest (socket); 353 // PrintWriter out = new PrintWriter (socket.getOutputStream()); 354 // out.print ("HTTP/1.0 200 OK\r\n"); 355 // out.print ("Content-Type: text/plain; charset="); 356 // out.print (Charset.defaultCharset() + "\r\n"); 357 // out.print ("\r\n"); 358 // if (request.isValid()) 359 // { 360 // out.print ("Method = \"" + request.getMethod() + "\"\r\n"); 361 // out.print ("URI = \"" + request.getUri() + "\"\r\n"); 362 // out.print ("Version = \"" + request.getHttpVersion() + "\"\r\n"); 363 // for (Map.Entry<String,String> entry : request.getHeaders()) 364 // { 365 // out.print ("Header name = \"" + entry.getKey()); 366 // out.print ("\", value = \"" + entry.getValue() + "\"\r\n"); 367 // } 368 // } 369 // else 370 // { 371 // out.print ("Invalid request\r\n"); 372 // } 373 // out.close(); 374 // socket.close(); 375 // } 376 // } 377 }