1 // ========================================================================
2 // Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd.
3 // ------------------------------------------------------------------------
4 // All rights reserved. This program and the accompanying materials
5 // are made available under the terms of the Eclipse Public License v1.0
6 // and Apache License v2.0 which accompanies this distribution.
7 // The Eclipse Public License is available at
8 // http://www.eclipse.org/legal/epl-v10.html
9 // The Apache License v2.0 is available at
10 // http://www.opensource.org/licenses/apache2.0.php
11 // You may elect to redistribute this code under either of these licenses.
12 // ========================================================================
13
14 package org.eclipse.jetty.testing;
15
16 import java.io.IOException;
17 import java.util.Enumeration;
18
19 import javax.servlet.http.Cookie;
20
21 import org.eclipse.jetty.http.HttpFields;
22 import org.eclipse.jetty.http.HttpGenerator;
23 import org.eclipse.jetty.http.HttpHeaders;
24 import org.eclipse.jetty.http.HttpParser;
25 import org.eclipse.jetty.http.HttpVersions;
26 import org.eclipse.jetty.http.MimeTypes;
27 import org.eclipse.jetty.io.Buffer;
28 import org.eclipse.jetty.io.ByteArrayBuffer;
29 import org.eclipse.jetty.io.EofException;
30 import org.eclipse.jetty.io.SimpleBuffers;
31 import org.eclipse.jetty.io.View;
32 import org.eclipse.jetty.io.bio.StringEndPoint;
33 import org.eclipse.jetty.util.ByteArrayOutputStream2;
34
35 /* ------------------------------------------------------------ */
36 /** Test support class.
37 * Assist with parsing and generating HTTP requests and responses.
38 *
39 * <pre>
40 * HttpTester tester = new HttpTester();
41 *
42 * tester.parse(
43 * "GET /uri HTTP/1.1\r\n"+
44 * "Host: fakehost\r\n"+
45 * "Content-Length: 10\r\n" +
46 * "\r\n");
47 *
48 * System.err.println(tester.getMethod());
49 * System.err.println(tester.getURI());
50 * System.err.println(tester.getVersion());
51 * System.err.println(tester.getHeader("Host"));
52 * System.err.println(tester.getContent());
53 * </pre>
54 *
55 *
56 * @see org.eclipse.jetty.testing.ServletTester
57 */
58 public class HttpTester
59 {
60 protected HttpFields _fields=new HttpFields();
61 protected String _method;
62 protected String _uri;
63 protected String _version;
64 protected int _status;
65 protected String _reason;
66 protected ByteArrayOutputStream2 _parsedContent;
67 protected byte[] _genContent;
68
69 private String _charset, _defaultCharset;
70 private Buffer _contentType;
71
72 public HttpTester()
73 {
74 this("UTF-8");
75 }
76
77 public HttpTester(String charset)
78 {
79 _defaultCharset = charset;
80 }
81
82 public void reset()
83 {
84 _fields.clear();
85 _method=null;
86 _uri=null;
87 _version=null;
88 _status=0;
89 _reason=null;
90 _parsedContent=null;
91 _genContent=null;
92 }
93
94 private String getString(Buffer buffer)
95 {
96 return getString(buffer.asArray());
97 }
98
99 private String getString(byte[] b)
100 {
101 if(_charset==null)
102 return new String(b);
103 try
104 {
105 return new String(b, _charset);
106 }
107 catch(Exception e)
108 {
109 return new String(b);
110 }
111 }
112
113 private byte[] getByteArray(String str)
114 {
115 if(_charset==null)
116 return str.getBytes();
117 try
118 {
119 return str.getBytes(_charset);
120 }
121 catch(Exception e)
122 {
123 return str.getBytes();
124 }
125 }
126
127 /* ------------------------------------------------------------ */
128 /**
129 * Parse one HTTP request or response
130 * @param rawHTTP Raw HTTP to parse
131 * @return Any unparsed data in the rawHTTP (eg pipelined requests)
132 * @throws IOException
133 */
134 public String parse(String rawHTTP, boolean isHeadResponse) throws IOException
135 {
136 _charset = _defaultCharset;
137 ByteArrayBuffer buf = new ByteArrayBuffer(getByteArray(rawHTTP));
138 View view = new View(buf);
139 PH ph = new PH();
140 HttpParser parser = new HttpParser(view,ph);
141 parser.setHeadResponse(isHeadResponse);
142 parser.parse();
143 if (ph.isEarlyEOF())
144 throw new EofException();
145 return getString(view.asArray());
146 }
147
148
149 /* ------------------------------------------------------------ */
150 /**
151 * Parse one HTTP request or response
152 * @param rawHTTP Raw HTTP to parse
153 * @return Any unparsed data in the rawHTTP (eg pipelined requests)
154 * @throws IOException
155 */
156 public String parse(String rawHTTP) throws IOException
157 {
158 return parse(rawHTTP, false);
159 }
160
161 /* ------------------------------------------------------------ */
162 /**
163 * Parse one HTTP request or response
164 * @param rawHTTP Raw HTTP to parse
165 * @return Any unparsed data in the rawHTTP (eg pipelined requests)
166 * @throws IOException
167 */
168 public byte[] parse(byte[] rawHTTP, boolean isHeadResponse) throws IOException
169 {
170 _charset = _defaultCharset;
171 ByteArrayBuffer buf = new ByteArrayBuffer(rawHTTP);
172 View view = new View(buf);
173 PH ph = new PH();
174 HttpParser parser = new HttpParser(view,ph);
175 parser.setHeadResponse(isHeadResponse);
176 parser.parse();
177 if (ph.isEarlyEOF())
178 throw new EofException();
179 return view.asArray();
180 }
181
182 /* ------------------------------------------------------------ */
183 /**
184 * Parse one HTTP request or response
185 * @param rawHTTP Raw HTTP to parse
186 * @return Any unparsed data in the rawHTTP (eg pipelined requests)
187 * @throws IOException
188 */
189 public byte[] parse(byte[] rawHTTP) throws IOException
190 {
191 return parse(rawHTTP, false);
192 }
193
194 /* ------------------------------------------------------------ */
195 public String generate() throws IOException
196 {
197 _charset = _defaultCharset;
198 _contentType = _fields.get(HttpHeaders.CONTENT_TYPE_BUFFER);
199 if(_contentType!=null)
200 {
201 String charset = MimeTypes.getCharsetFromContentType(_contentType);
202 if(charset!=null)
203 _charset = charset;
204 }
205 Buffer bb=new ByteArrayBuffer(32*1024 + (_genContent!=null?_genContent.length:0));
206 Buffer sb=new ByteArrayBuffer(4*1024);
207 StringEndPoint endp = new StringEndPoint(_charset);
208 HttpGenerator generator = new HttpGenerator(new SimpleBuffers(sb,bb),endp);
209
210 if (_method!=null)
211 {
212 generator.setRequest(getMethod(),getURI());
213 if (_version==null)
214 generator.setVersion(HttpVersions.HTTP_1_1_ORDINAL);
215 else
216 generator.setVersion(HttpVersions.CACHE.getOrdinal(HttpVersions.CACHE.lookup(_version)));
217 generator.completeHeader(_fields,false);
218 if (_genContent!=null)
219 generator.addContent(new View(new ByteArrayBuffer(_genContent)),false);
220 else if (_parsedContent!=null)
221 generator.addContent(new ByteArrayBuffer(_parsedContent.toByteArray()),false);
222 }
223
224 generator.complete();
225 generator.flushBuffer();
226 return endp.getOutput();
227 }
228
229 /* ------------------------------------------------------------ */
230 /**
231 * @return the method
232 */
233 public String getMethod()
234 {
235 return _method;
236 }
237
238 /* ------------------------------------------------------------ */
239 /**
240 * @param method the method to set
241 */
242 public void setMethod(String method)
243 {
244 _method=method;
245 }
246
247 /* ------------------------------------------------------------ */
248 /**
249 * @return the reason
250 */
251 public String getReason()
252 {
253 return _reason;
254 }
255
256 /* ------------------------------------------------------------ */
257 /**
258 * @param reason the reason to set
259 */
260 public void setReason(String reason)
261 {
262 _reason=reason;
263 }
264
265 /* ------------------------------------------------------------ */
266 /**
267 * @return the status
268 */
269 public int getStatus()
270 {
271 return _status;
272 }
273
274 /* ------------------------------------------------------------ */
275 /**
276 * @param status the status to set
277 */
278 public void setStatus(int status)
279 {
280 _status=status;
281 }
282
283 /* ------------------------------------------------------------ */
284 /**
285 * @return the uri
286 */
287 public String getURI()
288 {
289 return _uri;
290 }
291
292 /* ------------------------------------------------------------ */
293 /**
294 * @param uri the uri to set
295 */
296 public void setURI(String uri)
297 {
298 _uri=uri;
299 }
300
301 /* ------------------------------------------------------------ */
302 /**
303 * @return the version
304 */
305 public String getVersion()
306 {
307 return _version;
308 }
309
310 /* ------------------------------------------------------------ */
311 /**
312 * @param version the version to set
313 */
314 public void setVersion(String version)
315 {
316 _version=version;
317 }
318
319 /* ------------------------------------------------------------ */
320 public String getContentType()
321 {
322 return getString(_contentType);
323 }
324
325 /* ------------------------------------------------------------ */
326 public String getCharacterEncoding()
327 {
328 return _charset;
329 }
330
331 /* ------------------------------------------------------------ */
332 /**
333 * @param name
334 * @param value
335 * @throws IllegalArgumentException
336 * @see org.eclipse.jetty.http.HttpFields#add(java.lang.String, java.lang.String)
337 */
338 public void addHeader(String name, String value) throws IllegalArgumentException
339 {
340 _fields.add(name,value);
341 }
342
343 /* ------------------------------------------------------------ */
344 /**
345 * @param name
346 * @param date
347 * @see org.eclipse.jetty.http.HttpFields#addDateField(java.lang.String, long)
348 */
349 public void addDateHeader(String name, long date)
350 {
351 _fields.addDateField(name,date);
352 }
353
354 /* ------------------------------------------------------------ */
355 /**
356 * @param name
357 * @param value
358 * @see org.eclipse.jetty.http.HttpFields#addLongField(java.lang.String, long)
359 */
360 public void addLongHeader(String name, long value)
361 {
362 _fields.addLongField(name,value);
363 }
364
365 /* ------------------------------------------------------------ */
366 /**
367 * @param cookie
368 * @see org.eclipse.jetty.http.HttpFields#addSetCookie(org.eclipse.jetty.http.HttpCookie)
369 */
370 public void addSetCookie(Cookie cookie)
371 {
372 _fields.addSetCookie(
373 cookie.getName(),
374 cookie.getValue(),
375 cookie.getDomain(),
376 cookie.getPath(),
377 cookie.getMaxAge(),
378 cookie.getComment(),
379 cookie.getSecure(),
380 false,
381 cookie.getVersion());
382 }
383
384 /* ------------------------------------------------------------ */
385 /**
386 * @param name
387 * @return the header value as a date
388 * @see org.eclipse.jetty.http.HttpFields#getDateField(java.lang.String)
389 */
390 public long getDateHeader(String name)
391 {
392 return _fields.getDateField(name);
393 }
394
395 /* ------------------------------------------------------------ */
396 /**
397 * @return the header value names
398 * @see org.eclipse.jetty.http.HttpFields#getFieldNames()
399 */
400 public Enumeration getHeaderNames()
401 {
402 return _fields.getFieldNames();
403 }
404
405 /* ------------------------------------------------------------ */
406 /**
407 * @param name
408 * @return the header value as a long
409 * @throws NumberFormatException
410 * @see org.eclipse.jetty.http.HttpFields#getLongField(java.lang.String)
411 */
412 public long getLongHeader(String name) throws NumberFormatException
413 {
414 return _fields.getLongField(name);
415 }
416
417 /* ------------------------------------------------------------ */
418 /**
419 * @param name
420 * @return the header value
421 * @see org.eclipse.jetty.http.HttpFields#getStringField(java.lang.String)
422 */
423 public String getHeader(String name)
424 {
425 return _fields.getStringField(name);
426 }
427
428 /* ------------------------------------------------------------ */
429 /**
430 * @param name
431 * @return the header values
432 * @see org.eclipse.jetty.http.HttpFields#getValues(java.lang.String)
433 */
434 public Enumeration getHeaderValues(String name)
435 {
436 return _fields.getValues(name);
437 }
438
439 /* ------------------------------------------------------------ */
440 /**
441 * @param name
442 * @param value
443 * @see org.eclipse.jetty.http.HttpFields#put(java.lang.String, java.lang.String)
444 */
445 public void setHeader(String name, String value)
446 {
447 _fields.put(name,value);
448 }
449
450 /* ------------------------------------------------------------ */
451 /**
452 * @param name
453 * @param date
454 * @see org.eclipse.jetty.http.HttpFields#putDateField(java.lang.String, long)
455 */
456 public void setDateHeader(String name, long date)
457 {
458 _fields.putDateField(name,date);
459 }
460
461 /* ------------------------------------------------------------ */
462 /**
463 * @param name
464 * @param value
465 * @see org.eclipse.jetty.http.HttpFields#putLongField(java.lang.String, long)
466 */
467 public void setLongHeader(String name, long value)
468 {
469 _fields.putLongField(name,value);
470 }
471
472 /* ------------------------------------------------------------ */
473 /**
474 * @param name
475 * @see org.eclipse.jetty.http.HttpFields#remove(java.lang.String)
476 */
477 public void removeHeader(String name)
478 {
479 _fields.remove(name);
480 }
481
482 /* ------------------------------------------------------------ */
483 public String getContent()
484 {
485 if (_parsedContent!=null)
486 return getString(_parsedContent.toByteArray());
487 if (_genContent!=null)
488 return getString(_genContent);
489 return null;
490 }
491
492 /* ------------------------------------------------------------ */
493 public byte[] getContentBytes()
494 {
495 if (_parsedContent!=null)
496 return _parsedContent.toByteArray();
497 if (_genContent!=null)
498 return _genContent;
499 return null;
500 }
501
502 /* ------------------------------------------------------------ */
503 public void setContent(String content)
504 {
505 _parsedContent=null;
506 if (content!=null)
507 {
508 _genContent=getByteArray(content);
509 setLongHeader(HttpHeaders.CONTENT_LENGTH,_genContent.length);
510 }
511 else
512 {
513 removeHeader(HttpHeaders.CONTENT_LENGTH);
514 _genContent=null;
515 }
516 }
517
518 /* ------------------------------------------------------------ */
519 private class PH extends HttpParser.EventHandler
520 {
521 private volatile boolean _earlyEOF;
522
523 @Override
524 public void startRequest(Buffer method, Buffer url, Buffer version) throws IOException
525 {
526 reset();
527 _method=getString(method);
528 _uri=getString(url);
529 _version=getString(version);
530 }
531
532 @Override
533 public void startResponse(Buffer version, int status, Buffer reason) throws IOException
534 {
535 reset();
536 _version=getString(version);
537 _status=status;
538 _reason=getString(reason);
539 }
540
541 @Override
542 public void parsedHeader(Buffer name, Buffer value) throws IOException
543 {
544 _fields.add(name,value);
545 }
546
547 @Override
548 public void headerComplete() throws IOException
549 {
550 _contentType = _fields.get(HttpHeaders.CONTENT_TYPE_BUFFER);
551 if(_contentType!=null)
552 {
553 String charset = MimeTypes.getCharsetFromContentType(_contentType);
554 if(charset!=null)
555 _charset = charset;
556 }
557 }
558
559 @Override
560 public void messageComplete(long contextLength) throws IOException
561 {
562 }
563
564 @Override
565 public void content(Buffer ref) throws IOException
566 {
567 if (_parsedContent==null)
568 _parsedContent=new ByteArrayOutputStream2();
569 _parsedContent.write(ref.asArray());
570 }
571
572 @Override
573 public void earlyEOF()
574 {
575 _earlyEOF = true;
576 }
577
578 public boolean isEarlyEOF()
579 {
580 return _earlyEOF;
581 }
582
583 }
584
585 }