001: //========================================================================
002: //$Id: HttpConnection.java,v 1.13 2005/11/25 21:01:45 gregwilkins Exp $
003: //Copyright 2004-2005 Mort Bay Consulting Pty. Ltd.
004: //------------------------------------------------------------------------
005: //Licensed under the Apache License, Version 2.0 (the "License");
006: //you may not use this file except in compliance with the License.
007: //You may obtain a copy of the License at
008: //http://www.apache.org/licenses/LICENSE-2.0
009: //Unless required by applicable law or agreed to in writing, software
010: //distributed under the License is distributed on an "AS IS" BASIS,
011: //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012: //See the License for the specific language governing permissions and
013: //limitations under the License.
014: //========================================================================
015:
016: package org.mortbay.jetty;
017:
018: import java.io.IOException;
019: import java.io.InputStream;
020: import java.io.PrintWriter;
021:
022: import javax.servlet.ServletInputStream;
023: import javax.servlet.ServletOutputStream;
024: import javax.servlet.http.HttpServletResponse;
025:
026: import org.mortbay.io.Buffer;
027: import org.mortbay.io.Connection;
028: import org.mortbay.io.EndPoint;
029: import org.mortbay.log.Log;
030: import org.mortbay.util.URIUtil;
031: import org.mortbay.util.ajax.Continuation;
032:
033: /**
034: * <p>A HttpConnection represents the connection of a HTTP client to the server
035: * and is created by an instance of a {@link Connector}. It's prime function is
036: * to associate {@link Request} and {@link Response} instances with a {@link EndPoint}.
037: * </p>
038: * <p>
039: * A connection is also the prime mechanism used by jetty to recycle objects without
040: * pooling. The {@link Request}, {@link Response}, {@link HttpParser}, {@link HttpGenerator}
041: * and {@link HttpFields} instances are all recycled for the duraction of
042: * a connection. Where appropriate, allocated buffers are also kept associated
043: * with the connection via the parser and/or generator.
044: * </p>
045: *
046: *
047: * @author gregw
048: *
049: */
050: public class HttpConnection implements Connection {
051: private static int UNKNOWN = -2;
052: private static ThreadLocal __currentConnection = new ThreadLocal();
053:
054: private long _timeStamp = System.currentTimeMillis();
055: private int _requests;
056:
057: protected Connector _connector;
058: protected EndPoint _endp;
059: protected Server _server;
060:
061: protected HttpURI _uri = new HttpURI();
062:
063: protected Parser _parser;
064: protected HttpFields _requestFields;
065: protected Request _request;
066: protected ServletInputStream _in;
067:
068: protected Generator _generator;
069: protected HttpFields _responseFields;
070: protected Response _response;
071: protected Output _out;
072: protected OutputWriter _writer;
073: protected PrintWriter _printWriter;
074:
075: int _include;
076:
077: private Object _associatedObject; // associated object
078:
079: private transient int _connection = UNKNOWN;
080: private transient int _expect = UNKNOWN;
081: private transient int _version = UNKNOWN;
082: private transient boolean _head = false;
083: private transient boolean _host = false;
084: private transient boolean _delayedHandling = false;
085:
086: /* ------------------------------------------------------------ */
087: public static HttpConnection getCurrentConnection() {
088: return (HttpConnection) __currentConnection.get();
089: }
090:
091: /* ------------------------------------------------------------ */
092: protected static void setCurrentConnection(HttpConnection connection) {
093: __currentConnection.set(connection);
094: }
095:
096: /* ------------------------------------------------------------ */
097: /** Constructor
098: *
099: */
100: public HttpConnection(Connector connector, EndPoint endpoint,
101: Server server) {
102: _connector = connector;
103: _endp = endpoint;
104: _parser = new HttpParser(_connector, endpoint,
105: new RequestHandler(), _connector.getHeaderBufferSize(),
106: _connector.getRequestBufferSize());
107: _requestFields = new HttpFields();
108: _responseFields = new HttpFields();
109: _request = new Request(this );
110: _response = new Response(this );
111: _generator = new HttpGenerator(_connector, _endp, _connector
112: .getHeaderBufferSize(), _connector
113: .getResponseBufferSize());
114: _generator.setSendServerVersion(server.getSendServerVersion());
115: _server = server;
116: }
117:
118: /* ------------------------------------------------------------ */
119: public void destroy() {
120: if (_parser != null)
121: _parser.reset(true);
122:
123: if (_generator != null)
124: _generator.reset(true);
125:
126: if (_requestFields != null)
127: _requestFields.destroy();
128:
129: if (_responseFields != null)
130: _responseFields.destroy();
131:
132: _server = null;
133: }
134:
135: /* ------------------------------------------------------------ */
136: /**
137: * @return the parser used by this connection
138: */
139: public Parser getParser() {
140: return _parser;
141: }
142:
143: /* ------------------------------------------------------------ */
144: /**
145: * @return the number of requests handled by this connection
146: */
147: public int getRequests() {
148: return _requests;
149: }
150:
151: /* ------------------------------------------------------------ */
152: /**
153: * @return The time this connection was established.
154: */
155: public long getTimeStamp() {
156: return _timeStamp;
157: }
158:
159: /* ------------------------------------------------------------ */
160: /**
161: * @return Returns the associatedObject.
162: */
163: public Object getAssociatedObject() {
164: return _associatedObject;
165: }
166:
167: /* ------------------------------------------------------------ */
168: /**
169: * @param associatedObject The associatedObject to set.
170: */
171: public void setAssociatedObject(Object associatedObject) {
172: _associatedObject = associatedObject;
173: }
174:
175: /* ------------------------------------------------------------ */
176: /**
177: * @return Returns the connector.
178: */
179: public Connector getConnector() {
180: return _connector;
181: }
182:
183: /* ------------------------------------------------------------ */
184: /**
185: * @return Returns the requestFields.
186: */
187: public HttpFields getRequestFields() {
188: return _requestFields;
189: }
190:
191: /* ------------------------------------------------------------ */
192: /**
193: * @return Returns the responseFields.
194: */
195: public HttpFields getResponseFields() {
196: return _responseFields;
197: }
198:
199: /* ------------------------------------------------------------ */
200: /**
201: * @return The result of calling {@link #getConnector}.{@link Connector#isConfidential(Request) isCondidential}(request), or false
202: * if there is no connector.
203: */
204: public boolean isConfidential(Request request) {
205: if (_connector != null)
206: return _connector.isConfidential(request);
207: return false;
208: }
209:
210: /* ------------------------------------------------------------ */
211: /**
212: * Find out if the request is INTEGRAL security.
213: * @param request
214: * @return <code>true</code> if there is a {@link #getConnector() connector} and it considers <code>request</code>
215: * to be {@link Connector#isIntegral(Request) integral}
216: */
217: public boolean isIntegral(Request request) {
218: if (_connector != null)
219: return _connector.isIntegral(request);
220: return false;
221: }
222:
223: /* ------------------------------------------------------------ */
224: /**
225: * @return The {@link EndPoint} for this connection.
226: */
227: public EndPoint getEndPoint() {
228: return _endp;
229: }
230:
231: /* ------------------------------------------------------------ */
232: /**
233: * @return <code>false</code> (this method is not yet implemented)
234: */
235: public boolean getResolveNames() {
236: return _connector.getResolveNames();
237: }
238:
239: /* ------------------------------------------------------------ */
240: /**
241: * @return Returns the request.
242: */
243: public Request getRequest() {
244: return _request;
245: }
246:
247: /* ------------------------------------------------------------ */
248: /**
249: * @return Returns the response.
250: */
251: public Response getResponse() {
252: return _response;
253: }
254:
255: /* ------------------------------------------------------------ */
256: /**
257: * @return The input stream for this connection. The stream will be created if it does not already exist.
258: */
259: public ServletInputStream getInputStream() {
260: if (_in == null)
261: _in = new HttpParser.Input(((HttpParser) _parser),
262: _connector.getMaxIdleTime());
263: return _in;
264: }
265:
266: /* ------------------------------------------------------------ */
267: /**
268: * @return The output stream for this connection. The stream will be created if it does not already exist.
269: */
270: public ServletOutputStream getOutputStream() {
271: if (_out == null)
272: _out = new Output();
273: return _out;
274: }
275:
276: /* ------------------------------------------------------------ */
277: /**
278: * @return A {@link PrintWriter} wrapping the {@link #getOutputStream output stream}. The writer is created if it
279: * does not already exist.
280: */
281: public PrintWriter getPrintWriter(String encoding) {
282: getOutputStream();
283: if (_writer == null) {
284: _writer = new OutputWriter();
285: _printWriter = new PrintWriter(_writer) {
286: /* ------------------------------------------------------------ */
287: /*
288: * @see java.io.PrintWriter#close()
289: */
290: public void close() {
291: try {
292: _out.close();
293: } catch (IOException e) {
294: Log.debug(e);
295: setError();
296: }
297: }
298:
299: };
300: }
301: _writer.setCharacterEncoding(encoding);
302: return _printWriter;
303: }
304:
305: /* ------------------------------------------------------------ */
306: public boolean isResponseCommitted() {
307: return _generator.isCommitted();
308: }
309:
310: /* ------------------------------------------------------------ */
311: public synchronized void handle() throws IOException {
312: // Loop while more in buffer
313: boolean more_in_buffer = true; // assume true until proven otherwise
314: int no_progress = 0;
315:
316: while (more_in_buffer && _endp.isOpen()) {
317: try {
318: setCurrentConnection(this );
319: long io = 0;
320:
321: Continuation continuation = _request.getContinuation();
322: if (continuation != null && continuation.isPending()) {
323: Log.debug("resume continuation {}", continuation);
324: if (_request.getMethod() == null)
325: throw new IllegalStateException();
326: handleRequest();
327: } else {
328: // If we are not ended then parse available
329: if (!_parser.isComplete())
330: io = _parser.parseAvailable();
331:
332: // Do we have more writting to do?
333: if (_generator.isCommitted()
334: && !_generator.isComplete())
335: io += _generator.flush();
336:
337: if (_endp.isBufferingOutput()) {
338: _endp.flush();
339: if (_endp.isBufferingOutput())
340: no_progress = 0;
341: }
342:
343: if (io > 0)
344: no_progress = 0;
345: else if (no_progress++ >= 2)
346: return;
347: }
348: } catch (HttpException e) {
349: if (Log.isDebugEnabled()) {
350: Log.debug("uri=" + _uri);
351: Log.debug("fields=" + _requestFields);
352: Log.debug(e);
353: }
354: _generator.sendError(e.getStatus(), e.getReason(),
355: null, true);
356:
357: _parser.reset(true);
358: _endp.close();
359: throw e;
360: } finally {
361: setCurrentConnection(null);
362:
363: more_in_buffer = _parser.isMoreInBuffer()
364: || _endp.isBufferingInput();
365:
366: if (_parser.isComplete() && _generator.isComplete()
367: && !_endp.isBufferingOutput()) {
368: if (!_generator.isPersistent()) {
369: _parser.reset(true);
370: more_in_buffer = false;
371: }
372:
373: reset(!more_in_buffer);
374: }
375:
376: Continuation continuation = _request.getContinuation();
377: if (continuation != null && continuation.isPending()) {
378: break;
379: }
380: }
381: }
382: }
383:
384: /* ------------------------------------------------------------ */
385: protected void reset(boolean returnBuffers) {
386: _parser.reset(returnBuffers); // TODO maybe only release when low on resources
387: _requestFields.clear();
388: _request.recycle();
389:
390: _generator.reset(returnBuffers); // TODO maybe only release when low on resources
391: _responseFields.clear();
392: _response.recycle();
393:
394: _uri.clear();
395: }
396:
397: /* ------------------------------------------------------------ */
398: protected void handleRequest() throws IOException {
399: if (_server != null) {
400: boolean retrying = false;
401: boolean error = false;
402: String threadName = null;
403: try {
404: // TODO try to do this lazily or more efficiently
405: String info = URIUtil.canonicalPath(_uri
406: .getDecodedPath());
407: if (info == null)
408: throw new HttpException(400);
409: _request.setPathInfo(info);
410:
411: if (_out != null)
412: _out.reopen();
413:
414: if (Log.isDebugEnabled()) {
415: threadName = Thread.currentThread().getName();
416: Thread.currentThread().setName(
417: threadName + " - " + _uri);
418: }
419:
420: _connector.customize(_endp, _request);
421:
422: _server.handle(this );
423: } catch (RetryRequest r) {
424: Log.ignore(r);
425: retrying = true;
426: } catch (EofException e) {
427: Log.ignore(e);
428: error = true;
429: } catch (HttpException e) {
430: Log.debug(e);
431: _request.setHandled(true);
432: _response.sendError(e.getStatus(), e.getReason());
433: error = true;
434: } catch (Exception e) {
435: Log.warn(e);
436: _request.setHandled(true);
437: _generator.sendError(500, null, null, true);
438: error = true;
439: } catch (Error e) {
440: Log.warn(e);
441: _request.setHandled(true);
442: _generator.sendError(500, null, null, true);
443: error = true;
444: } finally {
445: if (threadName != null)
446: Thread.currentThread().setName(threadName);
447:
448: if (!retrying) {
449: if (_request.getContinuation() != null
450: && _request.getContinuation().isPending()) {
451: Log.debug("continuation still pending {}");
452: _request.getContinuation().reset();
453: }
454:
455: if (_endp.isOpen()) {
456: if (_generator.isPersistent())
457: _connector.persist(_endp);
458:
459: if (error)
460: _endp.close();
461: else {
462: if (!_response.isCommitted()
463: && !_request.isHandled())
464: _response
465: .sendError(HttpServletResponse.SC_NOT_FOUND);
466: _response.complete();
467: }
468: }
469: }
470: }
471: }
472: }
473:
474: /* ------------------------------------------------------------ */
475: public void commitResponse(boolean last) throws IOException {
476: if (!_generator.isCommitted()) {
477: _generator.setResponse(_response.getStatus(), _response
478: .getReason());
479: _generator.completeHeader(_responseFields, last);
480: }
481: if (last)
482: _generator.complete();
483: }
484:
485: /* ------------------------------------------------------------ */
486: public void completeResponse() throws IOException {
487: if (!_generator.isCommitted()) {
488: _generator.setResponse(_response.getStatus(), _response
489: .getReason());
490: _generator.completeHeader(_responseFields,
491: HttpGenerator.LAST);
492: }
493:
494: _generator.complete();
495: }
496:
497: /* ------------------------------------------------------------ */
498: public void flushResponse() throws IOException {
499: try {
500: commitResponse(HttpGenerator.MORE);
501: _generator.flush();
502: } catch (IOException e) {
503: throw (e instanceof EofException) ? e : new EofException(e);
504: }
505: }
506:
507: /* ------------------------------------------------------------ */
508: public Generator getGenerator() {
509: return _generator;
510: }
511:
512: /* ------------------------------------------------------------ */
513: public boolean isIncluding() {
514: return _include > 0;
515: }
516:
517: /* ------------------------------------------------------------ */
518: public void include() {
519: _include++;
520: }
521:
522: /* ------------------------------------------------------------ */
523: public void included() {
524: _include--;
525: if (_out != null)
526: _out.reopen();
527: }
528:
529: /* ------------------------------------------------------------ */
530: public boolean isIdle() {
531: return _generator.isIdle()
532: && (_parser.isIdle() || _delayedHandling);
533: }
534:
535: /* ------------------------------------------------------------ */
536: /* ------------------------------------------------------------ */
537: /* ------------------------------------------------------------ */
538: private class RequestHandler extends HttpParser.EventHandler {
539: private String _charset;
540:
541: /*
542: *
543: * @see org.mortbay.jetty.HttpParser.EventHandler#startRequest(org.mortbay.io.Buffer,
544: * org.mortbay.io.Buffer, org.mortbay.io.Buffer)
545: */
546: public void startRequest(Buffer method, Buffer uri,
547: Buffer version) throws IOException {
548: _host = false;
549: _expect = UNKNOWN;
550: _connection = UNKNOWN;
551: _delayedHandling = false;
552: _charset = null;
553:
554: _request.setTimeStamp(System.currentTimeMillis());
555: _request.setMethod(method.toString());
556:
557: try {
558: _uri.parse(uri.array(), uri.getIndex(), uri.length());
559: _request.setUri(_uri);
560:
561: if (version == null) {
562: _request.setProtocol(HttpVersions.HTTP_0_9);
563: _version = HttpVersions.HTTP_0_9_ORDINAL;
564: } else {
565: version = HttpVersions.CACHE.get(version);
566: _version = HttpVersions.CACHE.getOrdinal(version);
567: if (_version <= 0)
568: _version = HttpVersions.HTTP_1_0_ORDINAL;
569: _request.setProtocol(version.toString());
570: }
571:
572: _head = method == HttpMethods.HEAD_BUFFER; // depends on method being decached.
573: } catch (Exception e) {
574: throw new HttpException(
575: HttpStatus.ORDINAL_400_Bad_Request, null, e);
576: }
577: }
578:
579: /*
580: * @see org.mortbay.jetty.HttpParser.EventHandler#parsedHeaderValue(org.mortbay.io.Buffer)
581: */
582: public void parsedHeader(Buffer name, Buffer value) {
583: int ho = HttpHeaders.CACHE.getOrdinal(name);
584: switch (ho) {
585: case HttpHeaders.HOST_ORDINAL:
586: // TODO check if host matched a host in the URI.
587: _host = true;
588: break;
589:
590: case HttpHeaders.EXPECT_ORDINAL:
591: value = HttpHeaderValues.CACHE.lookup(value);
592: _expect = HttpHeaderValues.CACHE.getOrdinal(value);
593: break;
594:
595: case HttpHeaders.ACCEPT_ENCODING_ORDINAL:
596: case HttpHeaders.USER_AGENT_ORDINAL:
597: value = HttpHeaderValues.CACHE.lookup(value);
598: break;
599:
600: case HttpHeaders.CONTENT_TYPE_ORDINAL:
601: value = MimeTypes.CACHE.lookup(value);
602: _charset = MimeTypes.getCharsetFromContentType(value);
603: break;
604:
605: case HttpHeaders.CONNECTION_ORDINAL:
606:
607: _connection = HttpHeaderValues.CACHE.getOrdinal(value);
608: switch (_connection) {
609: case -1: {
610: // TODO coma list of connections ???
611: break;
612: }
613:
614: case HttpHeaderValues.CLOSE_ORDINAL:
615: _responseFields.put(HttpHeaders.CONNECTION_BUFFER,
616: HttpHeaderValues.CLOSE_BUFFER);
617: break;
618:
619: case HttpHeaderValues.KEEP_ALIVE_ORDINAL:
620: if (_version == HttpVersions.HTTP_1_0_ORDINAL)
621: _responseFields.put(
622: HttpHeaders.CONNECTION_BUFFER,
623: HttpHeaderValues.KEEP_ALIVE);
624: }
625: }
626:
627: _requestFields.add(name, value);
628: }
629:
630: /*
631: * @see org.mortbay.jetty.HttpParser.EventHandler#headerComplete()
632: */
633: public void headerComplete() throws IOException {
634: _requests++;
635: _generator.setVersion(_version);
636: switch (_version) {
637: case HttpVersions.HTTP_0_9_ORDINAL:
638: break;
639: case HttpVersions.HTTP_1_0_ORDINAL:
640: _generator.setHead(_head);
641: break;
642: case HttpVersions.HTTP_1_1_ORDINAL:
643: _generator.setHead(_head);
644: if (!_host) {
645: _generator.setResponse(
646: HttpStatus.ORDINAL_400_Bad_Request, null);
647: _responseFields.put(HttpHeaders.CONNECTION_BUFFER,
648: HttpHeaderValues.CLOSE_BUFFER);
649: _generator.completeHeader(_responseFields, true);
650: _generator.complete();
651: return;
652: }
653:
654: if (_expect != UNKNOWN) {
655: if (_expect == HttpHeaderValues.CONTINUE_ORDINAL) {
656: // TODO delay sending 100 response until a read is attempted.
657: if (((HttpParser) _parser).getHeaderBuffer() == null
658: || ((HttpParser) _parser)
659: .getHeaderBuffer().length() < 2) {
660: _generator.setResponse(
661: HttpStatus.ORDINAL_100_Continue,
662: null);
663: _generator.completeHeader(null, true);
664: _generator.complete();
665: _generator.reset(false);
666: }
667: } else if (_expect == HttpHeaderValues.PROCESSING_ORDINAL) {
668: } else {
669: _generator
670: .sendError(
671: HttpStatus.ORDINAL_417_Expectation_Failed,
672: null, null, true);
673: return;
674: }
675: }
676: break;
677: default:
678: }
679:
680: if (_charset != null)
681: _request.setCharacterEncodingUnchecked(_charset);
682:
683: // Either handle now or wait for first content
684: if (((HttpParser) _parser).getContentLength() <= 0
685: && !((HttpParser) _parser).isChunking())
686: handleRequest();
687: else
688: _delayedHandling = true;
689: }
690:
691: /* ------------------------------------------------------------ */
692: /*
693: * @see org.mortbay.jetty.HttpParser.EventHandler#content(int, org.mortbay.io.Buffer)
694: */
695: public void content(Buffer ref) throws IOException {
696: if (_delayedHandling) {
697: _delayedHandling = false;
698: handleRequest();
699: }
700: }
701:
702: /*
703: * (non-Javadoc)
704: *
705: * @see org.mortbay.jetty.HttpParser.EventHandler#messageComplete(int)
706: */
707: public void messageComplete(long contextLength)
708: throws IOException {
709: }
710:
711: /*
712: * (non-Javadoc)
713: *
714: * @see org.mortbay.jetty.HttpParser.EventHandler#startResponse(org.mortbay.io.Buffer, int,
715: * org.mortbay.io.Buffer)
716: */
717: public void startResponse(Buffer version, int status,
718: Buffer reason) {
719: throw new IllegalStateException("response");
720: }
721:
722: }
723:
724: /* ------------------------------------------------------------ */
725: /* ------------------------------------------------------------ */
726: /* ------------------------------------------------------------ */
727: public class Output extends AbstractGenerator.Output {
728: Output() {
729: super ((AbstractGenerator) HttpConnection.this ._generator,
730: _connector.getMaxIdleTime());
731: }
732:
733: /* ------------------------------------------------------------ */
734: /*
735: * @see java.io.OutputStream#close()
736: */
737: public void close() throws IOException {
738: if (_closed)
739: return;
740:
741: if (!isIncluding() && !_generator.isCommitted())
742: commitResponse(HttpGenerator.LAST);
743: else
744: flushResponse();
745:
746: super .close();
747: }
748:
749: /* ------------------------------------------------------------ */
750: /*
751: * @see java.io.OutputStream#flush()
752: */
753: public void flush() throws IOException {
754: if (!_generator.isCommitted())
755: commitResponse(HttpGenerator.MORE);
756: super .flush();
757: }
758:
759: /* ------------------------------------------------------------ */
760: /*
761: * @see javax.servlet.ServletOutputStream#print(java.lang.String)
762: */
763: public void print(String s) throws IOException {
764: if (_closed)
765: throw new IOException("Closed");
766: PrintWriter writer = getPrintWriter(null);
767: writer.print(s);
768: }
769:
770: /* ------------------------------------------------------------ */
771: public void sendContent(Object content) throws IOException {
772: if (_closed)
773: throw new IOException("Closed");
774:
775: if (_generator.getContentWritten() > 0)
776: throw new IllegalStateException("!empty");
777:
778: if (content instanceof HttpContent) {
779: HttpContent c = (HttpContent) content;
780: if (c.getContentType() != null
781: && !_responseFields
782: .containsKey(HttpHeaders.CONTENT_TYPE_BUFFER))
783: _responseFields.add(
784: HttpHeaders.CONTENT_TYPE_BUFFER, c
785: .getContentType());
786: if (c.getContentLength() > 0)
787: _responseFields.putLongField(
788: HttpHeaders.CONTENT_LENGTH_BUFFER, c
789: .getContentLength());
790: Buffer lm = c.getLastModified();
791: if (lm != null)
792: _responseFields.put(
793: HttpHeaders.LAST_MODIFIED_BUFFER, lm);
794: else if (c.getResource() != null) {
795: long lml = c.getResource().lastModified();
796: if (lml != -1)
797: _responseFields.putDateField(
798: HttpHeaders.LAST_MODIFIED_BUFFER, lml);
799: }
800:
801: content = c.getBuffer();
802: if (content == null)
803: content = c.getInputStream();
804: }
805:
806: if (content instanceof Buffer) {
807: _generator.addContent((Buffer) content,
808: HttpGenerator.LAST);
809: commitResponse(HttpGenerator.LAST);
810: } else if (content instanceof InputStream) {
811: InputStream in = (InputStream) content;
812:
813: int max = _generator.prepareUncheckedAddContent();
814: Buffer buffer = _generator.getUncheckedBuffer();
815:
816: int len = buffer.readFrom(in, max);
817:
818: while (len >= 0) {
819: _generator.completeUncheckedAddContent();
820: _out.flush();
821:
822: max = _generator.prepareUncheckedAddContent();
823: buffer = _generator.getUncheckedBuffer();
824: len = buffer.readFrom(in, max);
825: }
826: _generator.completeUncheckedAddContent();
827: _out.flush();
828: } else
829: throw new IllegalArgumentException(
830: "unknown content type?");
831: }
832: }
833:
834: /* ------------------------------------------------------------ */
835: /* ------------------------------------------------------------ */
836: /* ------------------------------------------------------------ */
837: public class OutputWriter extends AbstractGenerator.OutputWriter {
838: OutputWriter() {
839: super(HttpConnection.this._out);
840: }
841: }
842:
843: }
|