001: /**
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */package org.apache.openejb.server.httpd;
017:
018: import java.io.ByteArrayInputStream;
019: import java.io.ByteArrayOutputStream;
020: import java.io.DataInput;
021: import java.io.DataInputStream;
022: import java.io.EOFException;
023: import java.io.IOException;
024: import java.io.InputStream;
025: import java.net.URI;
026: import java.net.URISyntaxException;
027: import java.net.URLDecoder;
028: import java.util.HashMap;
029: import java.util.Map;
030: import java.util.StringTokenizer;
031:
032: /**
033: * A class to take care of HTTP Requests. It parses headers, content, form and url
034: * parameters.
035: */
036: public class HttpRequestImpl implements HttpRequest {
037: private static final String FORM_URL_ENCODED = "application/x-www-form-urlencoded";
038: private static final String TRANSFER_ENCODING = "Transfer-Encoding";
039: private static final String CHUNKED = "chunked";
040: protected static final String EJBSESSIONID = "EJBSESSIONID";
041:
042: /**
043: * 5.1.1 Method
044: */
045: private Method method;
046:
047: /**
048: * 5.1.2 Request-URI
049: */
050: private URI uri;
051:
052: /**
053: * the headers for this page
054: */
055: private final Map<String, String> headers = new HashMap<String, String>();
056:
057: /**
058: * the form parameters for this page
059: */
060: private final Map<String, String> formParams = new HashMap<String, String>();
061:
062: /**
063: * the URL (or query) parameters for this page
064: */
065: private final Map<String, String> queryParams = new HashMap<String, String>();
066:
067: /**
068: * All form and query parameters. Query parameters override form parameters.
069: */
070: private final Map<String, String> parameters = new HashMap<String, String>();
071:
072: /**
073: * Cookies sent from the client
074: */
075: private Map<String, String> cookies;
076:
077: /**
078: * the content of the body of the request
079: */
080: private byte[] body;
081: private InputStream in;
082: private int length;
083: private String contentType;
084:
085: /**
086: * the address the request came in on
087: */
088: private final URI socketURI;
089:
090: /**
091: * Request scoped data which is set and used by application code.
092: */
093: private final Map<String, Object> attributes = new HashMap<String, Object>();
094:
095: public HttpRequestImpl(URI socketURI) {
096: this .socketURI = socketURI;
097: }
098:
099: /**
100: * Gets a header based the header name passed in.
101: *
102: * @param name The name of the header to get
103: * @return The value of the header
104: */
105: public String getHeader(String name) {
106: return headers.get(name);
107: }
108:
109: /**
110: * Gets a form parameter based on the name passed in.
111: *
112: * @param name The name of the form parameter to get
113: * @return The value of the parameter
114: */
115: public String getFormParameter(String name) {
116: return formParams.get(name);
117: }
118:
119: public Map<String, String> getFormParameters() {
120: return new HashMap<String, String>(formParams);
121: }
122:
123: public Map<String, String> getQueryParameters() {
124: return new HashMap<String, String>(queryParams);
125: }
126:
127: /**
128: * Gets a URL (or query) parameter based on the name passed in.
129: *
130: * @param name The name of the URL (or query) parameter
131: * @return The value of the URL (or query) parameter
132: */
133: public String getQueryParameter(String name) {
134: return queryParams.get(name);
135: }
136:
137: /**
138: * Gets the request method.
139: * @return the request method
140: */
141: public Method getMethod() {
142: return method;
143: }
144:
145: /**
146: * Gets the URI for the current URL page.
147: *
148: * @return the URI
149: */
150: public URI getURI() {
151: return uri;
152: }
153:
154: public int getContentLength() {
155: return length;
156: }
157:
158: public String getContentType() {
159: return contentType;
160: }
161:
162: public InputStream getInputStream() throws IOException {
163: return this .in;
164: }
165:
166: /*------------------------------------------------------------*/
167: /* Methods for reading in and parsing a request */
168: /*------------------------------------------------------------*/
169: /**
170: * parses the request into the 3 different parts, request, headers, and body
171: *
172: * @param input the data input for this page
173: * @throws java.io.IOException if an exception is thrown
174: */
175: protected void readMessage(InputStream input) throws IOException {
176: DataInput in = new DataInputStream(input);
177:
178: readRequestLine(in);
179: readHeaders(in);
180: readBody(in);
181:
182: parameters.putAll(this .getFormParameters());
183: parameters.putAll(this .getQueryParameters());
184:
185: //temp-debug-------------------------------------------
186: // System.out.println("******************* HEADERS ******************");
187: // for (Map.Entry<String, String> entry : headers.entrySet()) {
188: // System.out.println(entry);
189: // }
190: // System.out.println("**********************************************");
191: // System.out.println(new String(body));
192: // System.out.println("**********************************************");
193: //end temp-debug---------------------------------------
194: }
195:
196: /**
197: * reads and parses the request line
198: *
199: * @param in the input to be read
200: * @throws java.io.IOException if an exception is thrown
201: */
202: private void readRequestLine(DataInput in) throws IOException {
203: String line;
204: try {
205: line = in.readLine();
206: // System.out.println(line);
207: } catch (Exception e) {
208: throw new IOException(
209: "Could not read the HTTP Request Line :"
210: + e.getClass().getName() + " : "
211: + e.getMessage());
212: }
213:
214: StringTokenizer lineParts = new StringTokenizer(line, " ");
215: /* [1] Parse the method */
216: parseMethod(lineParts);
217: /* [2] Parse the URI */
218: parseURI(lineParts);
219: }
220:
221: /**
222: * parses the method for this page
223: *
224: * @param lineParts a StringTokenizer of the request line
225: * @throws java.io.IOException if an exeption is thrown
226: */
227: private void parseMethod(StringTokenizer lineParts)
228: throws IOException {
229: String token;
230: try {
231: token = lineParts.nextToken();
232: } catch (Exception e) {
233: throw new IOException(
234: "Could not parse the HTTP Request Method :"
235: + e.getClass().getName() + " : "
236: + e.getMessage());
237: }
238:
239: if (token.equalsIgnoreCase("GET")) {
240: method = Method.GET;
241: } else if (token.equalsIgnoreCase("POST")) {
242: method = Method.POST;
243: } else {
244: method = Method.UNSUPPORTED;
245: throw new IOException("Unsupported HTTP Request Method :"
246: + token);
247: }
248: }
249:
250: /**
251: * parses the URI into the different parts
252: *
253: * @param lineParts a StringTokenizer of the URI
254: * @throws java.io.IOException if an exeption is thrown
255: */
256: private void parseURI(StringTokenizer lineParts) throws IOException {
257: String token;
258: try {
259: token = lineParts.nextToken();
260: } catch (Exception e) {
261: throw new IOException(
262: "Could not parse the HTTP Request Method :"
263: + e.getClass().getName() + " : "
264: + e.getMessage());
265: }
266:
267: try {
268: uri = new URI(socketURI.toString() + token);
269: } catch (URISyntaxException e) {
270: throw new IOException("Malformed URI :" + token
271: + " Exception: " + e.getMessage());
272: }
273:
274: parseQueryParams(uri.getQuery());
275: }
276:
277: /**
278: * parses the URL (or query) parameters
279: *
280: * @param query the URL (or query) parameters to be parsed
281: */
282: private void parseQueryParams(String query) {
283: if (query == null)
284: return;
285: StringTokenizer parameters = new StringTokenizer(query, "&");
286:
287: while (parameters.hasMoreTokens()) {
288: StringTokenizer param = new StringTokenizer(parameters
289: .nextToken(), "=");
290:
291: /* [1] Parse the Name */
292: if (!param.hasMoreTokens())
293: continue;
294: String name = URLDecoder.decode(param.nextToken());
295: if (name == null)
296: continue;
297:
298: String value;
299: /* [2] Parse the Value */
300: if (!param.hasMoreTokens()) {
301: value = "";
302: } else {
303: value = URLDecoder.decode(param.nextToken());
304: }
305:
306: //System.out.println("[] "+name+" = "+value);
307: queryParams.put(name, value);
308: }
309: }
310:
311: /**
312: * reads the headers from the data input sent from the browser
313: *
314: * @param in the data input sent from the browser
315: * @throws java.io.IOException if an exeption is thrown
316: */
317: private void readHeaders(DataInput in) throws IOException {
318: // System.out.println("\nREQUEST");
319: while (true) {
320: // Header Field
321: String hf;
322:
323: try {
324: hf = in.readLine();
325: //System.out.println(hf);
326: } catch (Exception e) {
327: throw new IOException(
328: "Could not read the HTTP Request Header Field :"
329: + e.getClass().getName() + " : "
330: + e.getMessage());
331: }
332:
333: if (hf == null || hf.equals("")) {
334: break;
335: }
336:
337: /* [1] parse the name */
338: int colonIndex = hf.indexOf((int) ':');
339: String name = hf.substring(0, colonIndex);
340: if (name == null)
341: break;
342:
343: /* [2] Parse the Value */
344: String value = hf.substring(colonIndex + 1, hf.length());
345: if (value == null)
346: break;
347: value = value.trim();
348: headers.put(name, value);
349: }
350:
351: // Update the URI to be what the client sees the the server as.
352: String host = headers.get("Host");
353: if (host != null) {
354: String hostName;
355: int port = uri.getPort();
356: int idx = host.indexOf(":");
357: if (idx >= 0) {
358: hostName = host.substring(0, idx);
359: try {
360: port = Integer.parseInt(host.substring(idx + 1));
361: } catch (NumberFormatException ignore) {
362: }
363: } else {
364: hostName = host;
365: }
366:
367: try {
368: uri = new URI(uri.getScheme(), uri.getUserInfo(),
369: hostName, port, uri.getPath(), uri.getQuery(),
370: uri.getFragment());
371: } catch (URISyntaxException ignore) {
372: }
373: }
374:
375: //temp-debug-------------------------------------------
376: //java.util.Iterator myKeys = headers.keySet().iterator();
377: //String temp = null;
378: //while(myKeys.hasNext()) {
379: // temp = (String)myKeys.next();
380: // System.out.println("Test: " + temp + "=" + headers.get(temp));
381: //}
382: //end temp-debug---------------------------------------
383: }
384:
385: /**
386: * reads the body from the data input passed in
387: *
388: * @param in the data input with the body of the page
389: * @throws java.io.IOException if an exception is thrown
390: */
391: private void readBody(DataInput in) throws IOException {
392: //System.out.println("Body Length: " + body.length);
393: // Content-type: application/x-www-form-urlencoded
394: // or multipart/form-data
395: length = parseContentLength();
396:
397: contentType = getHeader(HttpRequest.HEADER_CONTENT_TYPE);
398:
399: if (method == Method.POST
400: && FORM_URL_ENCODED.equals(contentType)) {
401: String rawParams;
402:
403: try {
404: body = readContent(in);
405: rawParams = new String(body);
406: } catch (Exception e) {
407: throw (IOException) new IOException(
408: "Could not read the HTTP Request Body: "
409: + e.getMessage()).initCause(e);
410: }
411:
412: StringTokenizer parameters = new StringTokenizer(rawParams,
413: "&");
414: String name;
415: String value;
416:
417: while (parameters.hasMoreTokens()) {
418: StringTokenizer param = new StringTokenizer(parameters
419: .nextToken(), "=");
420:
421: /* [1] Parse the Name */
422: name = URLDecoder.decode(param.nextToken());
423: if (name == null)
424: break;
425:
426: /* [2] Parse the Value */
427: if (param.hasMoreTokens()) {
428: value = URLDecoder.decode(param.nextToken());
429: } else {
430: value = ""; //if there is no token set value to blank string
431: }
432:
433: if (value == null)
434: value = "";
435:
436: formParams.put(name, value);
437: //System.out.println(name + ": " + value);
438: }
439: } else if (method == Method.POST
440: && CHUNKED.equals(headers.get(TRANSFER_ENCODING))) {
441: try {
442: ByteArrayOutputStream out = new ByteArrayOutputStream(
443: 4096);
444: for (String line = in.readLine(); line != null; line = in
445: .readLine()) {
446: // read the size line which is in hex
447: String sizeString = line.split(";", 2)[0];
448: int size = Integer.parseInt(sizeString, 16);
449:
450: // if size is 0 we are done
451: if (size == 0)
452: break;
453:
454: // read the chunk and append to byte array
455: byte[] chunk = new byte[size];
456: in.readFully(chunk);
457: out.write(chunk);
458:
459: // read off the trailing new line characters after the chunk
460: in.readLine();
461: }
462: body = out.toByteArray();
463: this .in = new ByteArrayInputStream(body);
464: } catch (Exception e) {
465: throw (IOException) new IOException(
466: "Unable to read chunked body").initCause(e);
467: }
468: } else if (method == Method.POST) {
469: // TODO This really is terrible
470: body = readContent(in);
471: this .in = new ByteArrayInputStream(body);
472: } else {
473: body = new byte[0];
474: this .in = new ByteArrayInputStream(body);
475: }
476:
477: }
478:
479: private byte[] readContent(DataInput in) throws IOException {
480: if (length >= 0) {
481: byte[] body = new byte[length];
482: in.readFully(body);
483: return body;
484: } else {
485: ByteArrayOutputStream out = new ByteArrayOutputStream(4096);
486: try {
487: boolean atLineStart = true;
488: while (true) {
489: byte b = in.readByte();
490:
491: if (b == '\r') {
492: // read the next byte
493: out.write(b);
494: b = in.readByte();
495: }
496:
497: if (b == '\n') {
498: if (atLineStart) {
499: // blank line signals end of data
500: break;
501: }
502: atLineStart = true;
503: } else {
504: atLineStart = false;
505: }
506: out.write(b);
507: }
508: } catch (EOFException e) {
509: // done reading
510: }
511: byte[] body = out.toByteArray();
512: return body;
513: }
514: }
515:
516: private int parseContentLength() {
517: // Content-length: 384
518: String len = getHeader(HttpRequest.HEADER_CONTENT_LENGTH);
519: //System.out.println("readRequestBody Content-Length: " + len);
520:
521: int length = -1;
522: if (len != null) {
523: try {
524: length = Integer.parseInt(len);
525: } catch (Exception e) {
526: //don't care
527: }
528: }
529: return length;
530: }
531:
532: protected Map getCookies() {
533: if (cookies != null)
534: return cookies;
535:
536: cookies = new HashMap<String, String>();
537:
538: String cookieHeader = getHeader(HEADER_COOKIE);
539: if (cookieHeader == null)
540: return cookies;
541:
542: StringTokenizer tokens = new StringTokenizer(cookieHeader, ";");
543: while (tokens.hasMoreTokens()) {
544: StringTokenizer token = new StringTokenizer(tokens
545: .nextToken(), "=");
546: String name = token.nextToken();
547: String value = token.nextToken();
548: cookies.put(name, value);
549: }
550: return cookies;
551: }
552:
553: protected String getCookie(String name) {
554: return (String) getCookies().get(name);
555: }
556:
557: public HttpSession getSession(boolean create) {
558: return null;
559: }
560:
561: public HttpSession getSession() {
562: return getSession(true);
563: }
564:
565: public Object getAttribute(String name) {
566: return attributes.get(name);
567: }
568:
569: public void setAttribute(String name, Object value) {
570: attributes.put(name, value);
571: }
572:
573: public String getParameter(String name) {
574: return parameters.get(name);
575: }
576:
577: public Map<String, String> getParameters() {
578: return new HashMap<String, String>(parameters);
579: }
580:
581: public String getRemoteAddr() {
582: // todo how do we get this value?
583: return null;
584: }
585: }
|