1
2
3
4
5
6
7
8
9
10
11
12
13 package org.abstracthorizon.danube.http;
14
15 import java.io.BufferedReader;
16 import java.io.EOFException;
17 import java.io.IOException;
18 import java.io.InputStream;
19 import java.io.InputStreamReader;
20 import java.io.OutputStream;
21 import java.io.PrintWriter;
22 import java.io.Reader;
23 import java.io.StringReader;
24 import java.io.UnsupportedEncodingException;
25 import java.io.Writer;
26 import java.net.URLDecoder;
27 import java.nio.charset.IllegalCharsetNameException;
28 import java.util.HashMap;
29 import java.util.Iterator;
30 import java.util.List;
31 import java.util.Map;
32
33 import org.abstracthorizon.danube.connection.Connection;
34 import org.abstracthorizon.danube.connection.ConnectionException;
35 import org.abstracthorizon.danube.connection.ConnectionHandler;
36 import org.abstracthorizon.danube.connection.ConnectionWrapper;
37 import org.abstracthorizon.danube.http.util.EncodingPrintWrtier;
38 import org.abstracthorizon.danube.http.util.IOUtils;
39 import org.abstracthorizon.danube.http.util.MultiStringHashMap;
40 import org.abstracthorizon.danube.http.util.MultiStringMap;
41 import org.abstracthorizon.danube.http.util.StringPrintWriter;
42 import org.abstracthorizon.danube.support.RuntimeIOException;
43
44
45
46
47
48
49
50
51
52
53
54
55
56 public class HTTPConnectionImpl extends ConnectionWrapper implements HTTPConnection {
57
58
59 protected static final String CRLF = "\r\n";
60
61
62 protected InputStream cachedInputStream;
63
64
65 protected OutputStream cachedOutputStream;
66
67
68 protected MultiStringHashMap requestHeaders = new MultiStringHashMap();
69
70
71
72
73
74
75 protected MultiStringHashMap requestParameters;
76
77
78 protected String requestMethod;
79
80
81 protected String requestProtocol;
82
83
84 protected String requestURI;
85
86
87 protected String requestPath;
88
89
90 protected int contextPointer = 0;
91
92
93 protected int componentPointer = 0;
94
95
96 protected String contextPath = "/";
97
98
99 protected String componentPath = "/";
100
101
102 protected String componentResourcePath;
103
104
105 protected MultiStringHashMap responseHeaders = new MultiStringHashMap();
106
107
108 protected Status responseStatus = Status.OK;
109
110
111 protected String responseProtocol;
112
113
114 protected boolean headersCommitted;
115
116
117 protected StringPrintWriter writer;
118
119
120 protected Map<String, Object> attributes;
121
122
123 protected ConnectionHandler parent;
124
125
126 protected boolean writerReturned = false;
127
128
129 protected boolean suppressOutput = false;
130
131
132 protected HTTPBufferedOutputStream bufferedOutput;
133
134
135 protected HTTPBufferedInputStream bufferedInput;
136
137
138 protected EncodingPrintWrtier cachedPrintWriter;
139
140
141 protected BufferedReader cachedBufferedReader;
142
143
144 protected String cachedReadersEncoding;
145
146
147 protected int defaultBufferSize;
148
149
150 protected boolean expectationIsHandled;
151
152
153
154
155
156
157
158
159 public HTTPConnectionImpl(Connection connection, ConnectionHandler parent, int defaultBufferSize) {
160 super(connection);
161 this.parent = parent;
162 cachedInputStream = (InputStream)connection.adapt(InputStream.class);
163 cachedOutputStream = (OutputStream)connection.adapt(OutputStream.class);
164 bufferedOutput = new HTTPBufferedOutputStream(this, cachedOutputStream, defaultBufferSize);
165 this.defaultBufferSize = defaultBufferSize;
166 }
167
168
169
170
171
172
173
174
175
176
177 public HTTPConnectionImpl(Connection connection, ConnectionHandler parent, InputStream inputStream, OutputStream outputStream, int defaultBufferSize) {
178 super(connection);
179 this.parent = parent;
180 cachedInputStream = inputStream;
181 cachedOutputStream = outputStream;
182 bufferedOutput = new HTTPBufferedOutputStream(this, cachedOutputStream, defaultBufferSize);
183 this.defaultBufferSize = defaultBufferSize;
184 }
185
186
187
188
189
190
191
192
193 public void reset() {
194 suppressOutput = false;
195 headersCommitted = false;
196 requestProtocol = null;
197 requestMethod = null;
198 contextPath = "/";
199 componentPath = "/";
200 componentResourcePath = null;
201 requestURI = null;
202 requestPath = null;
203 getRequestHeaders().clear();
204
205 bufferedOutput.resetInternals();
206 bufferedOutput.setBufferSize(defaultBufferSize);
207 if (bufferedInput != null) {
208 bufferedInput.resetInternals();
209 }
210 if (cachedPrintWriter != null) {
211 cachedPrintWriter.resetInternals();
212 }
213
214
215
216
217 requestParameters = null;
218
219 responseStatus = Status.OK;
220 getResponseHeaders().clear();
221
222 writerReturned = false;
223
224 if (writer != null) {
225 writer.reset();
226 }
227
228 if (attributes != null) {
229 attributes.clear();
230 }
231 expectationIsHandled = false;
232 }
233
234
235
236
237
238
239 public boolean isSuppressOutput() {
240 return suppressOutput;
241 }
242
243
244
245
246
247
248 public void setSuppressOutput(boolean suppressOutput) {
249 this.suppressOutput = suppressOutput;
250 bufferedOutput.setSupporessOutput(suppressOutput);
251 }
252
253
254
255
256
257
258 public int getBufferSize() {
259 return bufferedOutput.getBufferSize();
260 }
261
262
263
264
265
266
267 public void setBufferSize(int size) {
268 if (bufferedInput != null) {
269 bufferedInput.setBufferSize(size);
270 }
271 bufferedOutput.setBufferSize(size);
272 }
273
274
275
276
277
278
279
280
281 public void processRequest() throws IOException {
282 reset();
283
284 parseHttpRequestLine();
285 if ("HEAD".equals(requestMethod)) {
286 setSuppressOutput(true);
287 }
288 retrieveHeaders();
289 setupRequestPaths();
290
291
292
293
294 responseProtocol = requestProtocol;
295 }
296
297 protected void parseHttpRequestLine() throws IOException {
298 String request = readLine();
299 if (request == null) {
300 throw new EOFException();
301 }
302 int i = request.indexOf(' ');
303 if (i >= 0) {
304 requestMethod = request.substring(0, i);
305 int j = request.indexOf(' ', i+1);
306 if (i >= 0) {
307 requestURI = URLDecoder.decode(request.substring(i+1, j), "UTF-8");
308 requestProtocol = request.substring(j+1);
309 } else {
310 requestURI = request.substring(i+1);
311 }
312 }
313 }
314
315
316
317
318
319 protected void parseGetParameters() {
320 if (requestURI != null) {
321 int i = requestURI.indexOf('?');
322 if (i > 0) {
323 String params = requestURI.substring(i+1);
324 try {
325 retrieveParams(requestParameters, new StringReader(params), params.length());
326 } catch (IOException ignore) {
327 }
328 }
329 }
330 }
331
332
333
334
335 protected void setupRequestPaths() {
336 contextPointer = 0;
337 componentPointer = 0;
338 contextPath = "/";
339 if (requestURI != null) {
340 int i = requestURI.indexOf('?');
341 if (i > 0) {
342 componentResourcePath = requestURI.substring(0, i);
343 } else {
344 componentResourcePath = requestURI;
345 }
346 requestPath = componentResourcePath;
347 }
348 }
349
350
351
352
353
354 protected void parsePostParameters() throws IOException {
355 MultiStringMap requestHeaders = getRequestHeaders();
356 String contentLenString = requestHeaders.getOnly("Content-Length");
357 if (contentLenString != null) {
358 int len = Integer.parseInt(contentLenString);
359 if ("application/x-www-form-urlencoded".equals(requestHeaders.getOnly("Content-Type"))) {
360 retrieveParams(requestParameters, new InputStreamReader(getContentInputStream()), len);
361 }
362 }
363 }
364
365
366
367
368
369 protected void retrieveHeaders() throws IOException {
370 MultiStringMap requestHeaders = getRequestHeaders();
371 String line = readLine();
372 while ((line != null) && !"".equals(line)) {
373 int i = line.indexOf(':');
374 if (i >= 0) {
375 String header = line.substring(0, i);
376 String value = line.substring(i+2);
377 requestHeaders.add(header, value);
378 }
379
380 line = readLine();
381 }
382 if (bufferedInput != null) {
383 updateInputStreamLen();
384 }
385 }
386
387
388
389
390
391
392
393
394 protected static void retrieveParams(MultiStringMap params, Reader r, int len) throws IOException {
395
396
397 StringBuffer name = new StringBuffer();
398 StringBuffer value = new StringBuffer();
399 boolean retrieveName = true;
400 int i = 0;
401 while ((len > 0) && (i >= 0)) {
402 i = r.read();
403 len = len - 1;
404 if (i != -1) {
405 char c = (char)i;
406 if (c == '&') {
407 if (!retrieveName) {
408 addParam(params, name.toString(), value.toString());
409 name = new StringBuffer();
410 value = new StringBuffer();
411 retrieveName = true;
412 } else {
413
414 }
415 } else if (retrieveName && (c == '=')) {
416 retrieveName = false;
417 } else if (retrieveName) {
418 name.append(c);
419 } else {
420 value.append(c);
421 }
422 }
423 if ((i == -1) || (len == 0)) {
424 if (!retrieveName) {
425 addParam(params, name.toString(), value.toString());
426 } else {
427
428 }
429 }
430 }
431 }
432
433
434
435
436
437
438
439
440
441 public static void addParam(MultiStringMap params, String name, String value) {
442 try {
443 name = URLDecoder.decode(name, "UTF-8");
444 value = URLDecoder.decode(value, "UTF-8");
445 } catch (UnsupportedEncodingException ignore) {
446 }
447
448 params.add(name, value);
449 }
450
451
452
453
454
455
456 public String readLine() throws IOException {
457
458 InputStream in = cachedInputStream;
459 StringBuffer result = new StringBuffer();
460 int r = in.read();
461 if (r < 0) {
462 return null;
463 }
464 while ((r >= 0) && (r != '\n')) {
465 if (r >= ' ') {
466 result.append((char)r);
467 }
468 r = in.read();
469 }
470 return result.toString();
471 }
472
473
474
475
476
477
478 public MultiStringMap getRequestHeaders() {
479 if (requestHeaders == null) {
480 createRequestHeaders();
481 }
482 return requestHeaders;
483 }
484
485 protected void createRequestHeaders() {
486 requestHeaders = new MultiStringHashMap();
487 }
488
489
490
491
492
493
494
495
496 public MultiStringMap getRequestParameters() {
497 if (requestParameters == null) {
498 requestParameters = new MultiStringHashMap();
499 parseGetParameters();
500 if ("POST".equalsIgnoreCase(getRequestMethod())) {
501 try {
502 parsePostParameters();
503 } catch (IOException e) {
504 throw new ConnectionException(e);
505 }
506 }
507 }
508 return requestParameters;
509 }
510
511
512
513
514
515 public String getRequestMethod() {
516 return requestMethod;
517 }
518
519
520
521
522
523 public String getRequestProtocol() {
524 return requestProtocol;
525 }
526
527
528
529
530
531 public String getContextPath() {
532 return contextPath;
533 }
534
535
536
537
538 public void addComponentPathToContextPath() {
539
540
541
542
543 contextPath = IOUtils.addPaths(contextPath, componentPath);
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559 }
560
561
562
563
564
565 public String getComponentPath() {
566 return componentPath;
567 }
568
569
570
571
572
573
574
575 public void setComponentPath(String requestURI) {
576 this.componentPath = requestURI;
577 }
578
579
580
581
582
583 public String getComponentResourcePath() {
584 return componentResourcePath;
585 }
586
587
588
589
590
591
592 public void setComponentResourcePath(String resourcePath) {
593 this.componentResourcePath = resourcePath;
594 }
595
596
597
598
599
600 public String getRequestPath() {
601 return requestPath;
602 }
603
604
605
606
607
608
609 public String getRequestURI() {
610 return requestURI;
611 }
612
613
614
615
616
617 public MultiStringMap getResponseHeaders() {
618 if (responseHeaders == null) {
619 createResponseHeaders();
620 }
621 return responseHeaders;
622 }
623
624 protected void createResponseHeaders() {
625 responseHeaders = new MultiStringHashMap();
626 }
627
628
629
630
631
632 public Status getResponseStatus() {
633 return responseStatus;
634 }
635
636
637
638
639
640 public void setResponseStatus(Status status) {
641 this.responseStatus = status;
642 }
643
644
645
646
647
648 public String getResponseProtocol() {
649 return responseProtocol;
650 }
651
652
653
654
655
656 public void setResponseProtocol(String protocol) {
657 this.responseProtocol = protocol;
658 }
659
660
661
662
663
664 public boolean isCommited() {
665 return headersCommitted;
666 }
667
668
669
670
671
672 public void commitHeaders() {
673 if (!expectationIsHandled) {
674 handleExpectationHeader();
675 }
676 if (!headersCommitted) {
677 try {
678 if (requestProtocol.equals("HTTP/1.0")) {
679 if (!responseProtocol.equals(requestMethod)) {
680 responseProtocol = requestMethod;
681 }
682 }
683
684 MultiStringMap responseHeaders = getResponseHeaders();
685
686 String contentLength = responseHeaders.getOnly("Content-Length");
687 String responseStatusCode = responseStatus.getCode();
688
689 if (responseStatusCode.startsWith("1") || responseStatusCode.equals("204") || responseStatusCode.equals("304")) {
690 responseHeaders.putOnly("Content-Length", "0");
691 responseHeaders.removeAll("Transfer-Encoding");
692 bufferedOutput.setLimitedContentLength(0);
693 } else if (responseStatusCode.equals("200") && responseHeaders.containsKey("Content-Range")) {
694 responseStatus = Status.PARTIAL_CONTENT;
695 } else if ("HEAD".equals(requestMethod)) {
696
697 bufferedOutput.setLimitedContentLength(0);
698 } else {
699
700 int len = -1;
701 if (contentLength != null) {
702 try {
703 len = Integer.parseInt(contentLength);
704 } catch (NumberFormatException e) {
705 responseHeaders.removeAll("Content-Length");
706 len = -1;
707 }
708 }
709 bufferedOutput.setLimitedContentLength(len);
710 boolean chunkedEncoding = (len < 0) && "HTTP/1.1".equals(responseProtocol);
711 bufferedOutput.setChunkEncoding(chunkedEncoding);
712 if (chunkedEncoding) {
713 responseHeaders.removeAll("Content-Length");
714 responseHeaders.putOnly("Transfer-Encoding", "chunked");
715 }
716 }
717
718 StringBuffer headersBuffer = new StringBuffer(1024);
719 headersBuffer.append(responseProtocol).append(' ').append(responseStatus.getFullStatus()).append(CRLF);
720
721 Iterator<String> it = responseHeaders.keySet().iterator();
722 while (it.hasNext()) {
723 String key = it.next();
724 String[] headers = responseHeaders.getAsArray(key);
725 for (String header : headers) {
726 headersBuffer.append(key).append(": ").append(header).append(CRLF);
727 }
728 }
729 headersBuffer.append(CRLF);
730 cachedOutputStream.write(headersBuffer.toString().getBytes());
731 cachedOutputStream.flush();
732 headersCommitted = true;
733 } catch (IOException ioException) {
734 throw new RuntimeIOException(ioException);
735 }
736 }
737 }
738
739
740
741
742
743 public HTTPBufferedOutputStream getContentOutputStream() {
744 return bufferedOutput;
745 }
746
747
748
749
750
751 public HTTPBufferedInputStream getContentInputStream() {
752 if (!expectationIsHandled) {
753 handleExpectationHeader();
754 }
755 if (bufferedInput == null) {
756 bufferedInput = new HTTPBufferedInputStream(cachedInputStream, defaultBufferSize);
757 updateInputStreamLen();
758 }
759
760 return bufferedInput;
761 }
762
763 protected void handleExpectationHeader() {
764 if (!expectationIsHandled) {
765 MultiStringMap requestHeaders = getRequestHeaders();
766 String expect = requestHeaders.getOnly("Expect");
767 if ((expect != null) && ("100-continue".equals(expect))) {
768 String code = responseStatus.getCode();
769 StringBuffer headersBuffer = new StringBuffer(1024);
770 if (code.startsWith("2")) {
771 headersBuffer.append(responseProtocol).append(' ').append(Status.CONTINUE.getFullStatus()).append(CRLF);
772
773 } else {
774 headersBuffer.append(responseProtocol).append(' ').append(Status.EXPECTATION_FAILED.getFullStatus()).append(CRLF);
775 headersCommitted = true;
776 }
777
778 headersBuffer.append(CRLF);
779 try {
780 cachedOutputStream.write(headersBuffer.toString().getBytes());
781 cachedOutputStream.flush();
782 } catch (IOException e) {
783 throw new RuntimeIOException(e);
784 }
785 }
786 expectationIsHandled = true;
787 }
788 }
789
790
791
792
793
794 protected void updateInputStreamLen() {
795 MultiStringMap requestHeaders = getRequestHeaders();
796
797 boolean chunkedEncoding = "chunked".equals(requestHeaders.getOnly("Transfer-Encoding"));
798 bufferedInput.setChunkEncoding(chunkedEncoding);
799 if (!chunkedEncoding) {
800 long len = -1;
801 String contentLength = requestHeaders.getOnly("Content-Length");
802 if (contentLength != null) {
803 try {
804 len = Long.parseLong(contentLength);
805 } catch (NumberFormatException ignore) {
806 }
807 }
808 if (len >= 0) {
809 bufferedInput.setContentLength(len);
810 }
811 }
812 }
813
814
815
816
817
818
819 public PrintWriter getContentWriter() throws RuntimeIOException {
820 String encoding = getOutputEncoding();
821 if (cachedPrintWriter == null) {
822 try {
823 cachedPrintWriter = new EncodingPrintWrtier(getContentOutputStream(), encoding);
824 } catch (IllegalCharsetNameException e) {
825 throw new RuntimeException(e);
826 } catch (UnsupportedEncodingException e) {
827 throw new RuntimeIOException(e);
828 }
829 } else {
830 try {
831 cachedPrintWriter.setEncoding(encoding);
832 } catch (IllegalCharsetNameException e) {
833 throw new RuntimeException(e);
834 } catch (UnsupportedEncodingException e) {
835 throw new RuntimeIOException(e);
836 }
837 }
838 return cachedPrintWriter;
839 }
840
841
842
843
844
845 protected String getOutputEncoding() {
846 return null;
847 }
848
849
850
851
852
853 public BufferedReader getContentReader() {
854 String encoding = getInputEncoding();
855 if ((encoding != cachedReadersEncoding)
856 && ((encoding == null) || !encoding.equals(cachedReadersEncoding))) {
857 cachedBufferedReader = null;
858 }
859 if (cachedBufferedReader == null) {
860 if (encoding != null) {
861 try {
862 cachedBufferedReader = new BufferedReader(new InputStreamReader(getContentInputStream(), encoding));
863 } catch (UnsupportedEncodingException e) {
864 throw new RuntimeIOException(e);
865 }
866 } else {
867 cachedBufferedReader = new BufferedReader(new InputStreamReader(getContentInputStream()));
868 }
869 cachedReadersEncoding = encoding;
870 }
871 return cachedBufferedReader;
872 }
873
874
875
876
877
878 protected String getInputEncoding() {
879 return null;
880 }
881
882
883
884
885
886
887 public Map<String, Object> getAttributes() {
888 if (attributes == null) {
889 attributes = new HashMap<String, Object>();
890 }
891 return attributes;
892 }
893
894
895
896
897
898 public void forward(String uri) {
899
900
901
902 requestURI = uri;
903
904 componentResourcePath = null;
905 contextPath = "/";
906 componentPath = "/";
907 parseGetParameters();
908
909 parent.handleConnection(this);
910 }
911
912
913
914
915
916
917 @SuppressWarnings("unchecked")
918 public <T> T adapt(Class<T> cls) {
919 if (cls == HTTPConnection.class) {
920 return (T)this;
921 } else if ((cls == OutputStream.class) || (cls == HTTPBufferedOutputStream.class)) {
922 return (T)getContentOutputStream();
923 } else if ((cls == PrintWriter.class) || (cls == Writer.class) || (cls == EncodingPrintWrtier.class)) {
924 return (T)getContentWriter();
925 } else if ((cls == InputStream.class) || (cls == HTTPBufferedInputStream.class)) {
926 return (T)getContentInputStream();
927 } else if ((cls == Reader.class) || (cls == BufferedReader.class)) {
928 return (T)getContentReader();
929 } else {
930 return super.adapt(cls);
931 }
932 }
933
934 }