2 // ========================================================================
3 // Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
4 // ------------------------------------------------------------------------
5 // All rights reserved. This program and the accompanying materials
6 // are made available under the terms of the Eclipse Public License v1.0
7 // and Apache License v2.0 which accompanies this distribution.
9 // The Eclipse Public License is available at
10 // http://www.eclipse.org/legal/epl-v10.html
12 // The Apache License v2.0 is available at
13 // http://www.opensource.org/licenses/apache2.0.php
15 // You may elect to redistribute this code under either of these licenses.
16 // ========================================================================
19 package org.eclipse.jetty.http;
21 import java.io.IOException;
22 import java.nio.BufferOverflowException;
23 import java.nio.ByteBuffer;
24 import java.nio.charset.StandardCharsets;
25 import java.util.Arrays;
26 import java.util.HashSet;
29 import org.eclipse.jetty.http.HttpTokens.EndOfContent;
30 import org.eclipse.jetty.util.BufferUtil;
31 import org.eclipse.jetty.util.StringUtil;
32 import org.eclipse.jetty.util.log.Log;
33 import org.eclipse.jetty.util.log.Logger;
35 /* ------------------------------------------------------------ */
37 * HttpGenerator. Builds HTTP Messages.
39 * If the system property "org.eclipse.jetty.http.HttpGenerator.STRICT" is set to true,
40 * then the generator will strictly pass on the exact strings received from methods and header
41 * fields. Otherwise a fast case insensitive string lookup is used that may alter the
42 * case and white space of some methods/headers
45 public class HttpGenerator
47 private final static Logger LOG = Log.getLogger(HttpGenerator.class);
49 public final static boolean __STRICT=Boolean.getBoolean("org.eclipse.jetty.http.HttpGenerator.STRICT");
51 private final static byte[] __colon_space = new byte[] {':',' '};
52 private final static HttpHeaderValue[] CLOSE = {HttpHeaderValue.CLOSE};
53 public static final ResponseInfo CONTINUE_100_INFO = new ResponseInfo(HttpVersion.HTTP_1_1,null,-1,100,null,false);
54 public static final ResponseInfo PROGRESS_102_INFO = new ResponseInfo(HttpVersion.HTTP_1_1,null,-1,102,null,false);
55 public final static ResponseInfo RESPONSE_500_INFO =
56 new ResponseInfo(HttpVersion.HTTP_1_1,new HttpFields(){{put(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE);}},0,HttpStatus.INTERNAL_SERVER_ERROR_500,null,false);
59 public enum State { START, COMMITTED, COMPLETING, COMPLETING_1XX, END }
60 public enum Result { NEED_CHUNK,NEED_INFO,NEED_HEADER,FLUSH,CONTINUE,SHUTDOWN_OUT,DONE}
63 public static final int CHUNK_SIZE = 12;
65 private State _state = State.START;
66 private EndOfContent _endOfContent = EndOfContent.UNKNOWN_CONTENT;
68 private long _contentPrepared = 0;
69 private boolean _noContent = false;
70 private Boolean _persistent = null;
72 private final int _send;
73 private final static int SEND_SERVER = 0x01;
74 private final static int SEND_XPOWEREDBY = 0x02;
75 private final static Set<String> __assumedContentMethods = new HashSet<>(Arrays.asList(new String[]{HttpMethod.POST.asString(),HttpMethod.PUT.asString()}));
77 /* ------------------------------------------------------------------------------- */
78 public static void setJettyVersion(String serverVersion)
80 SEND[SEND_SERVER] = StringUtil.getBytes("Server: " + serverVersion + "\015\012");
81 SEND[SEND_XPOWEREDBY] = StringUtil.getBytes("X-Powered-By: " + serverVersion + "\015\012");
82 SEND[SEND_SERVER | SEND_XPOWEREDBY] = StringUtil.getBytes("Server: " + serverVersion + "\015\012X-Powered-By: " +
83 serverVersion + "\015\012");
86 /* ------------------------------------------------------------------------------- */
88 private boolean _needCRLF = false;
90 /* ------------------------------------------------------------------------------- */
91 public HttpGenerator()
96 /* ------------------------------------------------------------------------------- */
97 public HttpGenerator(boolean sendServerVersion,boolean sendXPoweredBy)
99 _send=(sendServerVersion?SEND_SERVER:0) | (sendXPoweredBy?SEND_XPOWEREDBY:0);
102 /* ------------------------------------------------------------------------------- */
105 _state = State.START;
106 _endOfContent = EndOfContent.UNKNOWN_CONTENT;
109 _contentPrepared = 0;
113 /* ------------------------------------------------------------ */
115 public boolean getSendServerVersion ()
117 return (_send&SEND_SERVER)!=0;
120 /* ------------------------------------------------------------ */
122 public void setSendServerVersion (boolean sendServerVersion)
124 throw new UnsupportedOperationException();
127 /* ------------------------------------------------------------ */
128 public State getState()
133 /* ------------------------------------------------------------ */
134 public boolean isState(State state)
136 return _state == state;
139 /* ------------------------------------------------------------ */
140 public boolean isIdle()
142 return _state == State.START;
145 /* ------------------------------------------------------------ */
146 public boolean isEnd()
148 return _state == State.END;
151 /* ------------------------------------------------------------ */
152 public boolean isCommitted()
154 return _state.ordinal() >= State.COMMITTED.ordinal();
157 /* ------------------------------------------------------------ */
158 public boolean isChunking()
160 return _endOfContent==EndOfContent.CHUNKED_CONTENT;
163 /* ------------------------------------------------------------ */
164 public boolean isNoContent()
169 /* ------------------------------------------------------------ */
170 public void setPersistent(boolean persistent)
172 _persistent=persistent;
175 /* ------------------------------------------------------------ */
177 * @return true if known to be persistent
179 public boolean isPersistent()
181 return Boolean.TRUE.equals(_persistent);
184 /* ------------------------------------------------------------ */
185 public boolean isWritten()
187 return _contentPrepared>0;
190 /* ------------------------------------------------------------ */
191 public long getContentPrepared()
193 return _contentPrepared;
196 /* ------------------------------------------------------------ */
204 /* ------------------------------------------------------------ */
205 public Result generateRequest(RequestInfo info, ByteBuffer header, ByteBuffer chunk, ByteBuffer content, boolean last) throws IOException
212 return Result.NEED_INFO;
214 // Do we need a request header
216 return Result.NEED_HEADER;
218 // If we have not been told our persistence, set the default
219 if (_persistent==null)
220 _persistent=(info.getHttpVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal());
222 // prepare the header
223 int pos=BufferUtil.flipToFill(header);
226 // generate ResponseLine
227 generateRequestLine(info,header);
229 if (info.getHttpVersion()==HttpVersion.HTTP_0_9)
232 generateHeaders(info,header,content,last);
234 boolean expect100 = info.getHttpFields().contains(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString());
238 _state = State.COMMITTED;
242 // handle the content.
243 int len = BufferUtil.length(content);
246 _contentPrepared+=len;
248 prepareChunk(header,len);
250 _state = last?State.COMPLETING:State.COMMITTED;
257 String message= (e instanceof BufferOverflowException)?"Response header too large":e.getMessage();
258 throw new IOException(message,e);
262 BufferUtil.flipToFlush(header,pos);
268 int len = BufferUtil.length(content);
272 // Do we need a chunk buffer?
275 // Do we need a chunk buffer?
277 return Result.NEED_CHUNK;
278 BufferUtil.clearToFill(chunk);
279 prepareChunk(chunk,len);
280 BufferUtil.flipToFlush(chunk,0);
282 _contentPrepared+=len;
287 _state=State.COMPLETING;
288 return len>0?Result.FLUSH:Result.CONTINUE;
296 if (BufferUtil.hasContent(content))
298 if (LOG.isDebugEnabled())
299 LOG.debug("discarding content in COMPLETING");
300 BufferUtil.clear(content);
305 // Do we need a chunk buffer?
307 return Result.NEED_CHUNK;
308 BufferUtil.clearToFill(chunk);
309 prepareChunk(chunk,0);
310 BufferUtil.flipToFlush(chunk,0);
311 _endOfContent=EndOfContent.UNKNOWN_CONTENT;
316 return Boolean.TRUE.equals(_persistent)?Result.DONE:Result.SHUTDOWN_OUT;
320 if (BufferUtil.hasContent(content))
322 if (LOG.isDebugEnabled())
323 LOG.debug("discarding content in COMPLETING");
324 BufferUtil.clear(content);
329 throw new IllegalStateException();
333 /* ------------------------------------------------------------ */
334 public Result generateResponse(ResponseInfo info, ByteBuffer header, ByteBuffer chunk, ByteBuffer content, boolean last) throws IOException
341 return Result.NEED_INFO;
344 if (info.getHttpVersion() == HttpVersion.HTTP_0_9)
347 _endOfContent=EndOfContent.EOF_CONTENT;
348 if (BufferUtil.hasContent(content))
349 _contentPrepared+=content.remaining();
350 _state = last?State.COMPLETING:State.COMMITTED;
354 // Do we need a response header
356 return Result.NEED_HEADER;
358 // If we have not been told our persistence, set the default
359 if (_persistent==null)
360 _persistent=(info.getHttpVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal());
362 // prepare the header
363 int pos=BufferUtil.flipToFill(header);
366 // generate ResponseLine
367 generateResponseLine(info,header);
369 // Handle 1xx and no content responses
370 int status=info.getStatus();
371 if (status>=100 && status<200 )
375 if (status!=HttpStatus.SWITCHING_PROTOCOLS_101 )
377 header.put(HttpTokens.CRLF);
378 _state=State.COMPLETING_1XX;
382 else if (status==HttpStatus.NO_CONTENT_204 || status==HttpStatus.NOT_MODIFIED_304)
387 generateHeaders(info,header,content,last);
389 // handle the content.
390 int len = BufferUtil.length(content);
393 _contentPrepared+=len;
394 if (isChunking() && !info.isHead())
395 prepareChunk(header,len);
397 _state = last?State.COMPLETING:State.COMMITTED;
401 String message= (e instanceof BufferOverflowException)?"Response header too large":e.getMessage();
402 throw new IOException(message,e);
406 BufferUtil.flipToFlush(header,pos);
414 int len = BufferUtil.length(content);
416 // handle the content.
422 return Result.NEED_CHUNK;
423 BufferUtil.clearToFill(chunk);
424 prepareChunk(chunk,len);
425 BufferUtil.flipToFlush(chunk,0);
427 _contentPrepared+=len;
432 _state=State.COMPLETING;
433 return len>0?Result.FLUSH:Result.CONTINUE;
435 return len>0?Result.FLUSH:Result.DONE;
447 if (BufferUtil.hasContent(content))
449 if (LOG.isDebugEnabled())
450 LOG.debug("discarding content in COMPLETING");
451 BufferUtil.clear(content);
456 // Do we need a chunk buffer?
458 return Result.NEED_CHUNK;
460 // Write the last chunk
461 BufferUtil.clearToFill(chunk);
462 prepareChunk(chunk,0);
463 BufferUtil.flipToFlush(chunk,0);
464 _endOfContent=EndOfContent.UNKNOWN_CONTENT;
470 return Boolean.TRUE.equals(_persistent)?Result.DONE:Result.SHUTDOWN_OUT;
474 if (BufferUtil.hasContent(content))
476 if (LOG.isDebugEnabled())
477 LOG.debug("discarding content in COMPLETING");
478 BufferUtil.clear(content);
483 throw new IllegalStateException();
487 /* ------------------------------------------------------------ */
488 private void prepareChunk(ByteBuffer chunk, int remaining)
490 // if we need CRLF add this to header
492 BufferUtil.putCRLF(chunk);
494 // Add the chunk size to the header
497 BufferUtil.putHexInt(chunk, remaining);
498 BufferUtil.putCRLF(chunk);
503 chunk.put(LAST_CHUNK);
508 /* ------------------------------------------------------------ */
509 private void generateRequestLine(RequestInfo request,ByteBuffer header)
511 header.put(StringUtil.getBytes(request.getMethod()));
512 header.put((byte)' ');
513 header.put(StringUtil.getBytes(request.getUri()));
514 switch(request.getHttpVersion())
518 header.put((byte)' ');
519 header.put(request.getHttpVersion().toBytes());
522 throw new IllegalStateException();
524 header.put(HttpTokens.CRLF);
527 /* ------------------------------------------------------------ */
528 private void generateResponseLine(ResponseInfo response, ByteBuffer header)
530 // Look for prepared response line
531 int status=response.getStatus();
532 PreparedResponse preprepared = status<__preprepared.length?__preprepared[status]:null;
533 String reason=response.getReason();
534 if (preprepared!=null)
537 header.put(preprepared._responseLine);
540 header.put(preprepared._schemeCode);
541 header.put(getReasonBytes(reason));
542 header.put(HttpTokens.CRLF);
545 else // generate response line
547 header.put(HTTP_1_1_SPACE);
548 header.put((byte) ('0' + status / 100));
549 header.put((byte) ('0' + (status % 100) / 10));
550 header.put((byte) ('0' + (status % 10)));
551 header.put((byte) ' ');
554 header.put((byte) ('0' + status / 100));
555 header.put((byte) ('0' + (status % 100) / 10));
556 header.put((byte) ('0' + (status % 10)));
559 header.put(getReasonBytes(reason));
560 header.put(HttpTokens.CRLF);
564 /* ------------------------------------------------------------ */
565 private byte[] getReasonBytes(String reason)
567 if (reason.length()>1024)
568 reason=reason.substring(0,1024);
569 byte[] _bytes = StringUtil.getBytes(reason);
571 for (int i=_bytes.length;i-->0;)
572 if (_bytes[i]=='\r' || _bytes[i]=='\n')
577 /* ------------------------------------------------------------ */
578 private void generateHeaders(Info _info,ByteBuffer header,ByteBuffer content,boolean last)
580 final RequestInfo request=(_info instanceof RequestInfo)?(RequestInfo)_info:null;
581 final ResponseInfo response=(_info instanceof ResponseInfo)?(ResponseInfo)_info:null;
583 // default field values
585 HttpField transfer_encoding=null;
586 boolean keep_alive=false;
588 boolean content_type=false;
589 StringBuilder connection = null;
592 if (_info.getHttpFields() != null)
594 for (HttpField field : _info.getHttpFields())
596 HttpHeader h = field.getHeader();
598 switch (h==null?HttpHeader.UNKNOWN:h)
601 // handle specially below
602 if (_info.getContentLength()>=0)
603 _endOfContent=EndOfContent.CONTENT_LENGTH;
608 if (field.getValue().startsWith(MimeTypes.Type.MULTIPART_BYTERANGES.toString()))
609 _endOfContent=EndOfContent.SELF_DEFINING_CONTENT;
611 // write the field to the header
617 case TRANSFER_ENCODING:
619 if (_info.getHttpVersion() == HttpVersion.HTTP_1_1)
620 transfer_encoding = field;
630 // Lookup and/or split connection value field
631 HttpHeaderValue[] values = HttpHeaderValue.CLOSE.is(field.getValue())?CLOSE:new HttpHeaderValue[]{HttpHeaderValue.CACHE.get(field.getValue())};
632 String[] split = null;
636 split = StringUtil.csvSplit(field.getValue());
639 values=new HttpHeaderValue[split.length];
640 for (int i=0;i<split.length;i++)
641 values[i]=HttpHeaderValue.CACHE.get(split[i]);
645 // Handle connection values
646 for (int i=0;i<values.length;i++)
648 HttpHeaderValue value=values[i];
649 switch (value==null?HttpHeaderValue.UNKNOWN:value)
653 // special case for websocket connection ordering
654 header.put(HttpHeader.CONNECTION.getBytesColonSpace()).put(HttpHeader.UPGRADE.getBytes());
665 if (_endOfContent == EndOfContent.UNKNOWN_CONTENT)
666 _endOfContent=EndOfContent.EOF_CONTENT;
673 if (_info.getHttpVersion() == HttpVersion.HTTP_1_0)
684 if (connection==null)
685 connection=new StringBuilder();
687 connection.append(',');
688 connection.append(split==null?field.getValue():split[i]);
699 send=send&~SEND_SERVER;
711 // Calculate how to end _content and connection, _content length and transfer encoding
713 // From RFC 2616 4.4:
714 // 1. No body for 1xx, 204, 304 & HEAD response
715 // 2. Force _content-length?
716 // 3. If Transfer-Encoding!=identity && HTTP/1.1 && !HttpConnection==close then chunk
718 // 5. multipart/byteranges
720 int status=response!=null?response.getStatus():-1;
721 switch (_endOfContent)
723 case UNKNOWN_CONTENT:
724 // It may be that we have no _content, or perhaps _content just has not been
727 // Response known not to have a body
728 if (_contentPrepared == 0 && response!=null && (status < 200 || status == 204 || status == 304))
729 _endOfContent=EndOfContent.NO_CONTENT;
730 else if (_info.getContentLength()>0)
732 // we have been given a content length
733 _endOfContent=EndOfContent.CONTENT_LENGTH;
734 long content_length = _info.getContentLength();
735 if ((response!=null || content_length>0 || content_type ) && !_noContent)
737 // known length but not actually set.
738 header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace());
739 BufferUtil.putDecLong(header, content_length);
740 header.put(HttpTokens.CRLF);
745 // we have seen all the _content there is, so we can be content-length limited.
746 _endOfContent=EndOfContent.CONTENT_LENGTH;
747 long content_length = _contentPrepared+BufferUtil.length(content);
749 // Do we need to tell the headers about it
750 if (content_length>0)
752 header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace());
753 BufferUtil.putDecLong(header, content_length);
754 header.put(HttpTokens.CRLF);
756 else if (!_noContent)
758 if (content_type || response!=null || (request!=null && __assumedContentMethods.contains(request.getMethod())))
759 header.put(CONTENT_LENGTH_0);
764 // No idea, so we must assume that a body is coming.
765 _endOfContent = EndOfContent.CHUNKED_CONTENT;
766 // HTTP 1.0 does not understand chunked content, so we must use EOF content.
767 // For a request with HTTP 1.0 & Connection: keep-alive
768 // we *must* close the connection, otherwise the client
769 // has no way to detect the end of the content.
770 if (!isPersistent() || _info.getHttpVersion().ordinal() < HttpVersion.HTTP_1_1.ordinal())
771 _endOfContent = EndOfContent.EOF_CONTENT;
776 long content_length = _info.getContentLength();
777 if (content_length>0)
779 header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace());
780 BufferUtil.putDecLong(header, content_length);
781 header.put(HttpTokens.CRLF);
783 else if (!_noContent)
785 if (content_type || response!=null || (request!=null && __assumedContentMethods.contains(request.getMethod())))
786 header.put(CONTENT_LENGTH_0);
791 throw new IllegalStateException();
794 _persistent = request!=null;
797 case CHUNKED_CONTENT:
804 // Add transfer_encoding if needed
807 // try to use user supplied encoding as it may have other values.
808 if (transfer_encoding != null && !HttpHeaderValue.CHUNKED.toString().equalsIgnoreCase(transfer_encoding.getValue()))
810 String c = transfer_encoding.getValue();
811 if (c.endsWith(HttpHeaderValue.CHUNKED.toString()))
812 putTo(transfer_encoding,header);
814 throw new IllegalArgumentException("BAD TE");
817 header.put(TRANSFER_ENCODING_CHUNKED);
820 // Handle connection if need be
821 if (_endOfContent==EndOfContent.EOF_CONTENT)
827 // If this is a response, work out persistence
830 if (!isPersistent() && (close || _info.getHttpVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal()))
832 if (connection==null)
833 header.put(CONNECTION_CLOSE);
836 header.put(CONNECTION_CLOSE,0,CONNECTION_CLOSE.length-2);
837 header.put((byte)',');
838 header.put(StringUtil.getBytes(connection.toString()));
844 if (connection==null)
845 header.put(CONNECTION_KEEP_ALIVE);
848 header.put(CONNECTION_KEEP_ALIVE,0,CONNECTION_KEEP_ALIVE.length-2);
849 header.put((byte)',');
850 header.put(StringUtil.getBytes(connection.toString()));
854 else if (connection!=null)
856 header.put(HttpHeader.CONNECTION.getBytesColonSpace());
857 header.put(StringUtil.getBytes(connection.toString()));
863 header.put(SEND[send]);
866 header.put(HttpTokens.CRLF);
869 /* ------------------------------------------------------------------------------- */
870 public static byte[] getReasonBuffer(int code)
872 PreparedResponse status = code<__preprepared.length?__preprepared[code]:null;
874 return status._reason;
878 /* ------------------------------------------------------------------------------- */
880 public String toString()
882 return String.format("%s{s=%s}",
883 getClass().getSimpleName(),
887 /* ------------------------------------------------------------------------------- */
888 /* ------------------------------------------------------------------------------- */
889 /* ------------------------------------------------------------------------------- */
891 private static final byte[] LAST_CHUNK = { (byte) '0', (byte) '\015', (byte) '\012', (byte) '\015', (byte) '\012'};
892 private static final byte[] CONTENT_LENGTH_0 = StringUtil.getBytes("Content-Length: 0\015\012");
893 private static final byte[] CONNECTION_KEEP_ALIVE = StringUtil.getBytes("Connection: keep-alive\015\012");
894 private static final byte[] CONNECTION_CLOSE = StringUtil.getBytes("Connection: close\015\012");
895 private static final byte[] HTTP_1_1_SPACE = StringUtil.getBytes(HttpVersion.HTTP_1_1+" ");
896 private static final byte[] CRLF = StringUtil.getBytes("\015\012");
897 private static final byte[] TRANSFER_ENCODING_CHUNKED = StringUtil.getBytes("Transfer-Encoding: chunked\015\012");
898 private static final byte[][] SEND = new byte[][]{
900 StringUtil.getBytes("Server: Jetty(9.x.x)\015\012"),
901 StringUtil.getBytes("X-Powered-By: Jetty(9.x.x)\015\012"),
902 StringUtil.getBytes("Server: Jetty(9.x.x)\015\012X-Powered-By: Jetty(9.x.x)\015\012")
905 /* ------------------------------------------------------------------------------- */
906 /* ------------------------------------------------------------------------------- */
907 /* ------------------------------------------------------------------------------- */
908 // Build cache of response lines for status
909 private static class PreparedResponse
913 byte[] _responseLine;
915 private static final PreparedResponse[] __preprepared = new PreparedResponse[HttpStatus.MAX_CODE+1];
918 int versionLength=HttpVersion.HTTP_1_1.toString().length();
920 for (int i=0;i<__preprepared.length;i++)
922 HttpStatus.Code code = HttpStatus.getCode(i);
925 String reason=code.getMessage();
926 byte[] line=new byte[versionLength+5+reason.length()+2];
927 HttpVersion.HTTP_1_1.toBuffer().get(line,0,versionLength);
928 line[versionLength+0]=' ';
929 line[versionLength+1]=(byte)('0'+i/100);
930 line[versionLength+2]=(byte)('0'+(i%100)/10);
931 line[versionLength+3]=(byte)('0'+(i%10));
932 line[versionLength+4]=' ';
933 for (int j=0;j<reason.length();j++)
934 line[versionLength+5+j]=(byte)reason.charAt(j);
935 line[versionLength+5+reason.length()]=HttpTokens.CARRIAGE_RETURN;
936 line[versionLength+6+reason.length()]=HttpTokens.LINE_FEED;
938 __preprepared[i] = new PreparedResponse();
939 __preprepared[i]._schemeCode = Arrays.copyOfRange(line, 0,versionLength+5);
940 __preprepared[i]._reason = Arrays.copyOfRange(line, versionLength+5, line.length-2);
941 __preprepared[i]._responseLine=line;
945 public static class Info
947 final HttpVersion _httpVersion;
948 final HttpFields _httpFields;
949 final long _contentLength;
951 private Info(HttpVersion httpVersion, HttpFields httpFields, long contentLength)
953 _httpVersion = httpVersion;
954 _httpFields = httpFields;
955 _contentLength = contentLength;
958 public HttpVersion getHttpVersion()
962 public HttpFields getHttpFields()
966 public long getContentLength()
968 return _contentLength;
972 public static class RequestInfo extends Info
974 private final String _method;
975 private final String _uri;
977 public RequestInfo(HttpVersion httpVersion, HttpFields httpFields, long contentLength, String method, String uri)
979 super(httpVersion,httpFields,contentLength);
984 public String getMethod()
989 public String getUri()
995 public String toString()
997 return String.format("RequestInfo{%s %s %s,%d}",_method,_uri,_httpVersion,_contentLength);
1001 public static class ResponseInfo extends Info
1003 private final int _status;
1004 private final String _reason;
1005 private final boolean _head;
1007 public ResponseInfo(HttpVersion httpVersion, HttpFields httpFields, long contentLength, int status, String reason, boolean head)
1009 super(httpVersion,httpFields,contentLength);
1015 public boolean isInformational()
1017 return _status>=100 && _status<200;
1020 public int getStatus()
1025 public String getReason()
1030 public boolean isHead()
1036 public String toString()
1038 return String.format("ResponseInfo{%s %s %s,%d,%b}",_httpVersion,_status,_reason,_contentLength,_head);
1042 private static void putSanitisedName(String s,ByteBuffer buffer)
1045 for (int i=0;i<l;i++)
1049 if (c<0 || c>0xff || c=='\r' || c=='\n'|| c==':')
1050 buffer.put((byte)'?');
1052 buffer.put((byte)(0xff&c));
1056 private static void putSanitisedValue(String s,ByteBuffer buffer)
1059 for (int i=0;i<l;i++)
1063 if (c<0 || c>0xff || c=='\r' || c=='\n')
1064 buffer.put((byte)' ');
1066 buffer.put((byte)(0xff&c));
1070 public static void putTo(HttpField field, ByteBuffer bufferInFillMode)
1072 if (field instanceof CachedHttpField)
1074 ((CachedHttpField)field).putTo(bufferInFillMode);
1078 HttpHeader header=field.getHeader();
1081 bufferInFillMode.put(header.getBytesColonSpace());
1082 putSanitisedValue(field.getValue(),bufferInFillMode);
1086 putSanitisedName(field.getName(),bufferInFillMode);
1087 bufferInFillMode.put(__colon_space);
1088 putSanitisedValue(field.getValue(),bufferInFillMode);
1091 BufferUtil.putCRLF(bufferInFillMode);
1095 public static void putTo(HttpFields fields, ByteBuffer bufferInFillMode)
1097 for (HttpField field : fields)
1100 putTo(field,bufferInFillMode);
1102 BufferUtil.putCRLF(bufferInFillMode);
1105 public static class CachedHttpField extends HttpField
1107 private final byte[] _bytes;
1108 public CachedHttpField(HttpHeader header,String value)
1110 super(header,value);
1111 int cbl=header.getBytesColonSpace().length;
1112 _bytes=Arrays.copyOf(header.getBytesColonSpace(), cbl+value.length()+2);
1113 System.arraycopy(value.getBytes(StandardCharsets.ISO_8859_1),0,_bytes,cbl,value.length());
1114 _bytes[_bytes.length-2]=(byte)'\r';
1115 _bytes[_bytes.length-1]=(byte)'\n';
1118 public void putTo(ByteBuffer bufferInFillMode)
1120 bufferInFillMode.put(_bytes);