/src/kilim/http/HttpRequest.java
Java | 376 lines | 270 code | 44 blank | 62 comment | 71 complexity | 947d177d671e38bc591b69779e34f080 MD5 | raw file
1/* Copyright (c) 2006, Sriram Srinivasan 2 * 3 * You may distribute this software under the terms of the license 4 * specified in the file "License" 5 */ 6 7package kilim.http; 8 9import java.io.IOException; 10import java.io.UnsupportedEncodingException; 11import java.net.URLDecoder; 12import java.nio.ByteBuffer; 13 14import kilim.Pausable; 15import kilim.nio.EndPoint; 16 17/** 18 * This object encapsulates a bytebuffer (via HttpMsg). HttpRequestParser creates an instance of this object, but only 19 * converts a few of the important fields into Strings; the rest are maintained as ranges (offset + length) in the 20 * bytebuffer. Use {@link #getHeader(String)} to get the appropriate field. 21 */ 22public class HttpRequest extends HttpMsg { 23 // All the header related members of this class are initialized by the HttpRequestParser class. 24 25 /** 26 * The original header. All string variables that pertain to the message's header are either subsequences of this 27 * header, or interned (all known keywords). 28 */ 29 public String method; 30 31 /** 32 * The UTF8 decoded path from the HTTP header. 33 */ 34 public String uriPath; 35 36 public int nFields; 37 /** 38 * Keys present in the HTTP header 39 */ 40 public String keys[]; 41 42 // range variables encode the offset and length within the header. The strings corresponding 43 // to these variables are created lazily. 44 public int versionRange; 45 public int uriFragmentRange; 46 public int queryStringRange; 47 public int[] valueRanges; 48 49 public int contentOffset; 50 public int contentLength; 51 52 /** 53 * The read cursor, used in the read* methods. 54 */ 55 public int iread; 56 57 public HttpRequest() { 58 keys = new String[5]; 59 valueRanges = new int[5]; 60 } 61 62 /** 63 * Get the value for a given key 64 * @param key 65 * @return null if the key is not present in the header. 66 */ 67 public String getHeader(String key) { 68 for (int i = 0; i < nFields; i++) { 69 if (key.equalsIgnoreCase(keys[i])) { 70 return extractRange(valueRanges[i]); 71 } 72 } 73 return ""; // no point returning null 74 } 75 76 /** 77 * @return the query part of the URI. 78 */ 79 public String getQuery() { 80 return extractRange(queryStringRange); 81 } 82 83 public String version() { 84 return extractRange(versionRange); 85 } 86 87 public boolean keepAlive() { 88 return isOldHttp() ? "Keep-Alive".equals(getHeader("Connection")) : !("close".equals(getHeader("Connection"))); 89 } 90 public KeyValues getQueryComponents() { 91 String q = getQuery(); 92 return getQueryComponents(q); 93 } 94 public KeyValues getQueryComponents(String q) { 95 int len = q.length(); 96 if (q == null || len == 0) 97 return new KeyValues(0); 98 99 int numPairs = 0; 100 for (int i = 0; i < len; i++) { 101 if (q.charAt(i) == '=') 102 numPairs++; 103 } 104 KeyValues components = new KeyValues(numPairs); 105 106 int beg = 0; 107 String key = null; 108 boolean url_encoded = false; 109 for (int i = 0; i <= len; i++) { 110 char c = (i == len) ? '&' // pretending there's an artificial marker at the end of the string, to capture 111 // the last component 112 : q.charAt(i); 113 114 if (c == '+' || c == '%') 115 url_encoded = true; 116 if (c == '=' || c == '&') { 117 String comp = q.substring(beg, i); 118 if (url_encoded) { 119 try { 120 comp = URLDecoder.decode(comp, "UTF-8"); 121 } catch (UnsupportedEncodingException ignore) { 122 } 123 } 124 if (key == null) { 125 key = comp; 126 } else { 127 components.put(key, comp); 128 key = null; 129 } 130 beg = i + 1; 131 url_encoded = false; // for next time 132 } 133 } 134 return components; 135 } 136 137 public String uriFragment() { 138 return extractRange(uriFragmentRange); 139 } 140 141 public String toString() { 142 StringBuilder sb = new StringBuilder(500); 143 sb.append("method: ").append(method).append('\n').append("version: ").append(version()).append('\n').append( 144 "path = ").append(uriPath).append('\n').append("uri_fragment = ").append(uriFragment()).append('\n') 145 .append("query = ").append(getQueryComponents()).append('\n'); 146 for (int i = 0; i < nFields; i++) { 147 sb.append(keys[i]).append(": ").append(extractRange(valueRanges[i])).append('\n'); 148 } 149 150 return sb.toString(); 151 } 152 153 /** 154 * @return true if version is 1.0 or earlier 155 */ 156 public boolean isOldHttp() { 157 final byte b1 = (byte) '1'; 158 int offset = versionRange >> 16; 159 return (buffer.get(offset) < b1 || buffer.get(offset + 2) < b1); 160 } 161 162 /** 163 * Clear the request object so that it can be reused for the next message. 164 */ 165 public void reuse() { 166 method = null; 167 uriPath = null; 168 versionRange = 0; 169 uriFragmentRange = queryStringRange = 0; 170 contentOffset = 0; 171 contentLength = 0; 172 173 if (buffer != null) { 174 buffer.clear(); 175 } 176 for (int i = 0; i < nFields; i++) { 177 keys[i] = null; 178 } 179 nFields = 0; 180 } 181 182 183 /* 184 * Internal methods 185 */ 186 public void readFrom(EndPoint endpoint) throws Pausable, IOException { 187 iread = 0; 188 readHeader(endpoint); 189 readBody(endpoint); 190 } 191 192 public void readHeader(EndPoint endpoint) throws Pausable, IOException { 193 buffer = ByteBuffer.allocate(1024); 194 int headerLength = 0; 195 int n; 196 do { 197 n = readLine(endpoint); // includes 2 bytes for CRLF 198 headerLength += n; 199 } while (n > 2 || headerLength <= 2); // until blank line (CRLF), but just blank line is not enough. 200 // dumpBuffer(buffer); 201 HttpRequestParser.initHeader(this, headerLength); 202 contentOffset = headerLength; // doesn't mean there's necessarily any content. 203 String cl = getHeader("Content-Length"); 204 if (cl.length() > 0) { 205 try { 206 contentLength = Integer.parseInt(cl); 207 } catch (NumberFormatException nfe) { 208 throw new IOException("Malformed Content-Length hdr"); 209 } 210 } else if ((getHeader("Transfer-Encoding").indexOf("chunked") >= 0) 211 || (getHeader("TE").indexOf("chunked") >= 0)) { 212 contentLength = -1; 213 } else { 214 contentLength = 0; 215 } 216 } 217 218 public void dumpBuffer(ByteBuffer buffer) { 219 byte[] ba = buffer.array(); 220 int len = buffer.position(); 221 for (int i = 0; i < len; i++) { 222 System.out.print((char) ba[i]); 223 } 224 } 225 226 public void addField(String key, int valRange) { 227 if (keys.length == nFields) { 228 keys = (String[]) Utils.growArray(keys, 5); 229 valueRanges = Utils.growArray(valueRanges, 5); 230 } 231 keys[nFields] = key; 232 valueRanges[nFields] = valRange; 233 nFields++; 234 } 235 236 237 // complement of HttpRequestParser.encodeRange 238 public String extractRange(int range) { 239 int beg = range >> 16; 240 int end = range & 0xFFFF; 241 return extractRange(beg, end); 242 } 243 244 public String extractRange(int beg, int end) { 245 return new String(buffer.array(), beg, (end - beg)); 246 } 247 public byte [] extractBytes(int beg, int end) { 248 return java.util.Arrays.copyOfRange(buffer.array(),beg,end); 249 } 250 251 252 /* 253 * Read entire content into request's buffer 254 */ 255 public void readBody(EndPoint endpoint) throws Pausable, IOException { 256 iread = contentOffset; 257 if (contentLength > 0) { 258 fill(endpoint, contentOffset, contentLength); 259 iread = contentOffset + contentLength; 260 } else if (contentLength == -1) { 261 // CHUNKED 262 readAllChunks(endpoint); 263 } 264 readTrailers(endpoint); 265 } 266 267 public void readTrailers(EndPoint endpoint) { 268 } 269 270 /* 271 * Read all chunks until a chunksize of 0 is received, then consolidate the chunks into a single contiguous chunk. 272 * At the end of this method, the entire content is available in the requests buffer, starting at contentOffset and 273 * of length contentLength. 274 */ 275 public void readAllChunks(EndPoint endpoint) throws IOException, Pausable { 276 IntList chunkRanges = new IntList(); // alternate numbers in this list refer to the start and end offsets of chunks. 277 do { 278 int n = readLine(endpoint); // read chunk size text into buffer 279 int beg = iread; 280 int size = parseChunkSize(buffer, iread - n, iread); // Parse size in hex, ignore extension 281 if (size == 0) 282 break; 283 // If the chunk has not already been read in, do so 284 fill(endpoint, iread, size+2 /*chunksize + CRLF*/); 285 // record chunk start and end 286 chunkRanges.add(beg); 287 chunkRanges.add(beg + size); // without the CRLF 288 iread += size + 2; // for the next round. 289 } while (true); 290 291 // / consolidate all chunkRanges 292 if (chunkRanges.numElements == 0) { 293 contentLength = 0; 294 return; 295 } 296 contentOffset = chunkRanges.get(0); // first chunk's beginning 297 int endOfLastChunk = chunkRanges.get(1); // first chunk's end 298 299 byte[] bufa = buffer.array(); 300 for (int i = 2; i < chunkRanges.numElements; i += 2) { 301 int beg = chunkRanges.get(i); 302 int chunkSize = chunkRanges.get(i + 1) - beg; 303 System.arraycopy(bufa, beg, bufa, endOfLastChunk, chunkSize); 304 endOfLastChunk += chunkSize; 305 } 306 // TODO move all trailer stuff up 307 contentLength = endOfLastChunk - contentOffset; 308 309 // At this point, the contentOffset and contentLen give the entire content 310 } 311 312 313 public static byte CR = (byte) '\r'; 314 public static byte LF = (byte) '\n'; 315 static final byte b0 = (byte) '0', b9 = (byte) '9'; 316 static final byte ba = (byte) 'a', bf = (byte) 'f'; 317 static final byte bA = (byte) 'A', bF = (byte) 'F'; 318 static final byte SEMI = (byte)';'; 319 320 public static int parseChunkSize(ByteBuffer buffer, int start, int end) throws IOException { 321 byte[] bufa = buffer.array(); 322 int size = 0; 323 for (int i = start; i < end; i++) { 324 byte b = bufa[i]; 325 if (b >= b0 && b <= b9) { 326 size = size * 16 + (b - b0); 327 } else if (b >= ba && b <= bf) { 328 size = size * 16 + ((b - ba) + 10); 329 } else if (b >= bA && b <= bF) { 330 size = size * 16 + ((b - bA) + 10); 331 } else if (b == CR || b == SEMI) { 332 // SEMI-colon starts a chunk extension. We ignore extensions currently. 333 break; 334 } else { 335 throw new IOException("Error parsing chunk size; unexpected char " + b + " at offset " + i); 336 } 337 } 338 return size; 339 } 340 341 // topup if request's buffer doesn't have all the bytes yet. 342 public void fill(EndPoint endpoint, int offset, int size) throws IOException, Pausable { 343 int total = offset + size; 344 int currentPos = buffer.position(); 345 if (total > buffer.position()) { 346 buffer = endpoint.fill(buffer, (total - currentPos)); 347 } 348 } 349 350 public int readLine(EndPoint endpoint) throws IOException, Pausable { 351 int ireadSave = iread; 352 int i = ireadSave; 353 while (true) { 354 int end = buffer.position(); 355 byte[] bufa = buffer.array(); 356 for (; i < end; i++) { 357 if (bufa[i] == CR) { 358 ++i; 359 if (i >= end) { 360 buffer = endpoint.fill(buffer, 1); 361 bufa = buffer.array(); // fill could have changed the buffer. 362 end = buffer.position(); 363 } 364 if (bufa[i] != LF) { 365 throw new IOException("Expected LF at " + i); 366 } 367 ++i; 368 int lineLength = i - ireadSave; 369 iread = i; 370 return lineLength; 371 } 372 } 373 buffer = endpoint.fill(buffer, 1); // no CRLF found. fill a bit more and start over. 374 } 375 } 376}