/src/kilim/http/HttpRequest.java

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