com.firefly.codec.http2.encode.HttpGenerator Maven / Gradle / Ivy
package com.firefly.codec.http2.encode;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import com.firefly.codec.http2.model.BadMessageException;
import com.firefly.codec.http2.model.HttpField;
import com.firefly.codec.http2.model.HttpFields;
import com.firefly.codec.http2.model.HttpHeader;
import com.firefly.codec.http2.model.HttpHeaderValue;
import com.firefly.codec.http2.model.HttpMethod;
import com.firefly.codec.http2.model.HttpStatus;
import com.firefly.codec.http2.model.HttpTokens;
import com.firefly.codec.http2.model.HttpTokens.EndOfContent;
import com.firefly.codec.http2.model.HttpVersion;
import com.firefly.codec.http2.model.MetaData;
import com.firefly.codec.http2.model.PreEncodedHttpField;
import com.firefly.utils.StringUtils;
import com.firefly.utils.io.BufferUtils;
import com.firefly.utils.log.Log;
import com.firefly.utils.log.LogFactory;
/**
* HttpGenerator. Builds HTTP Messages.
*
* If the system property "org.fireflysource.http.HttpGenerator.STRICT" is set
* to true, then the generator will strictly pass on the exact strings received
* from methods and header fields. Otherwise a fast case insensitive string
* lookup is used that may alter the case and white space of some
* methods/headers
*/
public class HttpGenerator {
private static Log log = LogFactory.getInstance().getLog("firefly-system");
private final static byte[] __colon_space = new byte[] { ':', ' ' };
private final static HttpHeaderValue[] CLOSE = { HttpHeaderValue.CLOSE };
public static final MetaData.Response CONTINUE_100_INFO = new MetaData.Response(HttpVersion.HTTP_1_1, 100, null, null, -1);
public static final MetaData.Response PROGRESS_102_INFO = new MetaData.Response(HttpVersion.HTTP_1_1, 102, null, null, -1);
public final static MetaData.Response RESPONSE_500_INFO = new MetaData.Response(HttpVersion.HTTP_1_1,
HttpStatus.INTERNAL_SERVER_ERROR_500, null, new HttpFields() {{
put(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE);
}}, 0);
// states
public enum State {
START, COMMITTED, COMPLETING, COMPLETING_1XX, END
}
public enum Result {
NEED_CHUNK, NEED_INFO, NEED_HEADER, FLUSH, CONTINUE, SHUTDOWN_OUT, DONE
}
// other statics
public static final int CHUNK_SIZE = 12;
private State _state = State.START;
private EndOfContent _endOfContent = EndOfContent.UNKNOWN_CONTENT;
private long _contentPrepared = 0;
private boolean _noContent = false;
private Boolean _persistent = null;
private final int _send;
private final static int SEND_SERVER = 0x01;
private final static int SEND_XPOWEREDBY = 0x02;
private final static Set __assumedContentMethods = new HashSet<>(
Arrays.asList(new String[] { HttpMethod.POST.asString(), HttpMethod.PUT.asString() }));
public static void setFireflyVersion(String serverVersion) {
SEND[SEND_SERVER] = StringUtils.getBytes("Server: " + serverVersion + "\015\012");
SEND[SEND_XPOWEREDBY] = StringUtils.getBytes("X-Powered-By: " + serverVersion + "\015\012");
SEND[SEND_SERVER | SEND_XPOWEREDBY] = StringUtils
.getBytes("Server: " + serverVersion + "\015\012X-Powered-By: " + serverVersion + "\015\012");
}
// data
private boolean _needCRLF = false;
public HttpGenerator() {
this(false, false);
}
public HttpGenerator(boolean sendServerVersion, boolean sendXPoweredBy) {
_send = (sendServerVersion ? SEND_SERVER : 0) | (sendXPoweredBy ? SEND_XPOWEREDBY : 0);
}
public void reset() {
_state = State.START;
_endOfContent = EndOfContent.UNKNOWN_CONTENT;
_noContent = false;
_persistent = null;
_contentPrepared = 0;
_needCRLF = false;
}
public State getState() {
return _state;
}
public boolean isState(State state) {
return _state == state;
}
public boolean isIdle() {
return _state == State.START;
}
public boolean isEnd() {
return _state == State.END;
}
public boolean isCommitted() {
return _state.ordinal() >= State.COMMITTED.ordinal();
}
public boolean isChunking() {
return _endOfContent == EndOfContent.CHUNKED_CONTENT;
}
public boolean isNoContent() {
return _noContent;
}
public void setPersistent(boolean persistent) {
_persistent = persistent;
}
/**
* @return true if known to be persistent
*/
public boolean isPersistent() {
return Boolean.TRUE.equals(_persistent);
}
public boolean isWritten() {
return _contentPrepared > 0;
}
public long getContentPrepared() {
return _contentPrepared;
}
public void abort() {
_persistent = false;
_state = State.END;
_endOfContent = null;
}
public Result generateRequest(MetaData.Request info, ByteBuffer header, ByteBuffer chunk, ByteBuffer content,
boolean last) {
switch (_state) {
case START: {
if (info == null)
return Result.NEED_INFO;
if (header == null)
return Result.NEED_HEADER;
// If we have not been told our persistence, set the default
if (_persistent == null) {
_persistent = info.getVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal();
if (!_persistent && HttpMethod.CONNECT.is(info.getMethod()))
_persistent = true;
}
// prepare the header
int pos = BufferUtils.flipToFill(header);
try {
// generate ResponseLine
generateRequestLine(info, header);
if (info.getVersion() == HttpVersion.HTTP_0_9)
throw new BadMessageException(500, "HTTP/0.9 not supported");
generateHeaders(info, header, content, last);
boolean expect100 = info.getFields().contains(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString());
if (expect100) {
_state = State.COMMITTED;
} else {
// handle the content.
int len = BufferUtils.length(content);
if (len > 0) {
_contentPrepared += len;
if (isChunking())
prepareChunk(header, len);
}
_state = last ? State.COMPLETING : State.COMMITTED;
}
return Result.FLUSH;
} catch (Exception e) {
String message = (e instanceof BufferOverflowException) ? "Request header too large" : e.getMessage();
throw new BadMessageException(500, message, e);
} finally {
BufferUtils.flipToFlush(header, pos);
}
}
case COMMITTED: {
int len = BufferUtils.length(content);
if (len > 0) {
// Do we need a chunk buffer?
if (isChunking()) {
// Do we need a chunk buffer?
if (chunk == null)
return Result.NEED_CHUNK;
BufferUtils.clearToFill(chunk);
prepareChunk(chunk, len);
BufferUtils.flipToFlush(chunk, 0);
}
_contentPrepared += len;
}
if (last)
_state = State.COMPLETING;
return len > 0 ? Result.FLUSH : Result.CONTINUE;
}
case COMPLETING: {
if (BufferUtils.hasContent(content)) {
if (log.isDebugEnabled())
log.debug("discarding content in COMPLETING");
BufferUtils.clear(content);
}
if (isChunking()) {
// Do we need a chunk buffer?
if (chunk == null)
return Result.NEED_CHUNK;
BufferUtils.clearToFill(chunk);
prepareChunk(chunk, 0);
BufferUtils.flipToFlush(chunk, 0);
_endOfContent = EndOfContent.UNKNOWN_CONTENT;
return Result.FLUSH;
}
_state = State.END;
return Boolean.TRUE.equals(_persistent) ? Result.DONE : Result.SHUTDOWN_OUT;
}
case END:
if (BufferUtils.hasContent(content)) {
if (log.isDebugEnabled())
log.debug("discarding content in COMPLETING");
BufferUtils.clear(content);
}
return Result.DONE;
default:
throw new IllegalStateException();
}
}
public Result generateResponse(MetaData.Response info, ByteBuffer header, ByteBuffer chunk, ByteBuffer content,
boolean last) {
return generateResponse(info, false, header, chunk, content, last);
}
public Result generateResponse(MetaData.Response info, boolean head, ByteBuffer header, ByteBuffer chunk,
ByteBuffer content, boolean last) {
switch (_state) {
case START: {
if (info == null)
return Result.NEED_INFO;
HttpVersion version = info.getVersion();
if (version == null)
throw new BadMessageException(500, "No version");
switch (version) {
case HTTP_1_0:
if (_persistent == null)
_persistent = Boolean.FALSE;
break;
case HTTP_1_1:
if (_persistent == null)
_persistent = Boolean.TRUE;
break;
default:
_persistent = false;
_endOfContent = EndOfContent.EOF_CONTENT;
if (BufferUtils.hasContent(content)) {
_contentPrepared += content.remaining();
}
_state = last ? State.COMPLETING : State.COMMITTED;
return Result.FLUSH;
}
// Do we need a response header
if (header == null)
return Result.NEED_HEADER;
// prepare the header
int pos = BufferUtils.flipToFill(header);
try {
// generate ResponseLine
generateResponseLine(info, header);
// Handle 1xx and no content responses
int status = info.getStatus();
if (status >= 100 && status < 200) {
_noContent = true;
if (status != HttpStatus.SWITCHING_PROTOCOLS_101) {
header.put(HttpTokens.CRLF);
_state = State.COMPLETING_1XX;
return Result.FLUSH;
}
} else if (status == HttpStatus.NO_CONTENT_204 || status == HttpStatus.NOT_MODIFIED_304) {
_noContent = true;
}
generateHeaders(info, header, content, last);
// handle the content.
int len = BufferUtils.length(content);
if (len > 0) {
_contentPrepared += len;
if (isChunking() && !head)
prepareChunk(header, len);
}
_state = last ? State.COMPLETING : State.COMMITTED;
} catch (Exception e) {
String message = (e instanceof BufferOverflowException) ? "Response header too large" : e.getMessage();
throw new BadMessageException(500, message, e);
} finally {
BufferUtils.flipToFlush(header, pos);
}
return Result.FLUSH;
}
case COMMITTED: {
int len = BufferUtils.length(content);
// handle the content.
if (len > 0) {
if (isChunking()) {
if (chunk == null)
return Result.NEED_CHUNK;
BufferUtils.clearToFill(chunk);
prepareChunk(chunk, len);
BufferUtils.flipToFlush(chunk, 0);
}
_contentPrepared += len;
}
if (last) {
_state = State.COMPLETING;
return len > 0 ? Result.FLUSH : Result.CONTINUE;
}
return len > 0 ? Result.FLUSH : Result.DONE;
}
case COMPLETING_1XX: {
reset();
return Result.DONE;
}
case COMPLETING: {
if (BufferUtils.hasContent(content)) {
if (log.isDebugEnabled())
log.debug("discarding content in COMPLETING");
BufferUtils.clear(content);
}
if (isChunking()) {
// Do we need a chunk buffer?
if (chunk == null)
return Result.NEED_CHUNK;
// Write the last chunk
BufferUtils.clearToFill(chunk);
prepareChunk(chunk, 0);
BufferUtils.flipToFlush(chunk, 0);
_endOfContent = EndOfContent.UNKNOWN_CONTENT;
return Result.FLUSH;
}
_state = State.END;
return Boolean.TRUE.equals(_persistent) ? Result.DONE : Result.SHUTDOWN_OUT;
}
case END:
if (BufferUtils.hasContent(content)) {
if (log.isDebugEnabled())
log.debug("discarding content in COMPLETING");
BufferUtils.clear(content);
}
return Result.DONE;
default:
throw new IllegalStateException();
}
}
private void prepareChunk(ByteBuffer chunk, int remaining) {
// if we need CRLF add this to header
if (_needCRLF)
BufferUtils.putCRLF(chunk);
// Add the chunk size to the header
if (remaining > 0) {
BufferUtils.putHexInt(chunk, remaining);
BufferUtils.putCRLF(chunk);
_needCRLF = true;
} else {
chunk.put(LAST_CHUNK);
_needCRLF = false;
}
}
private void generateRequestLine(MetaData.Request request, ByteBuffer header) {
header.put(StringUtils.getBytes(request.getMethod()));
header.put((byte) ' ');
header.put(StringUtils.getBytes(request.getURIString()));
header.put((byte) ' ');
header.put(request.getVersion().toBytes());
header.put(HttpTokens.CRLF);
}
private void generateResponseLine(MetaData.Response response, ByteBuffer header) {
// Look for prepared response line
int status = response.getStatus();
PreparedResponse preprepared = status < __preprepared.length ? __preprepared[status] : null;
String reason = response.getReason();
if (preprepared != null) {
if (reason == null)
header.put(preprepared._responseLine);
else {
header.put(preprepared._schemeCode);
header.put(getReasonBytes(reason));
header.put(HttpTokens.CRLF);
}
} else // generate response line
{
header.put(HTTP_1_1_SPACE);
header.put((byte) ('0' + status / 100));
header.put((byte) ('0' + (status % 100) / 10));
header.put((byte) ('0' + (status % 10)));
header.put((byte) ' ');
if (reason == null) {
header.put((byte) ('0' + status / 100));
header.put((byte) ('0' + (status % 100) / 10));
header.put((byte) ('0' + (status % 10)));
} else
header.put(getReasonBytes(reason));
header.put(HttpTokens.CRLF);
}
}
private byte[] getReasonBytes(String reason) {
if (reason.length() > 1024)
reason = reason.substring(0, 1024);
byte[] _bytes = StringUtils.getBytes(reason);
for (int i = _bytes.length; i-- > 0;)
if (_bytes[i] == '\r' || _bytes[i] == '\n')
_bytes[i] = '?';
return _bytes;
}
private void generateHeaders(MetaData _info, ByteBuffer header, ByteBuffer content, boolean last) {
final MetaData.Request request = (_info instanceof MetaData.Request) ? (MetaData.Request) _info : null;
final MetaData.Response response = (_info instanceof MetaData.Response) ? (MetaData.Response) _info : null;
// default field values
int send = _send;
HttpField transfer_encoding = null;
boolean keep_alive = false;
boolean close = false;
boolean content_type = false;
StringBuilder connection = null;
long content_length = _info.getContentLength();
// Generate fields
HttpFields fields = _info.getFields();
if (fields != null) {
int n = fields.size();
for (int f = 0; f < n; f++) {
HttpField field = fields.getField(f);
String v = field.getValue();
if (v == null || v.length() == 0)
continue; // rfc7230 does not allow no value
HttpHeader h = field.getHeader();
if (h == null)
putTo(field, header);
else {
switch (h) {
case CONTENT_LENGTH:
_endOfContent = EndOfContent.CONTENT_LENGTH;
if (content_length < 0)
content_length = Long.valueOf(field.getValue());
// handle setting the field specially below
break;
case CONTENT_TYPE: {
// write the field to the header
content_type = true;
putTo(field, header);
break;
}
case TRANSFER_ENCODING: {
if (_info.getVersion() == HttpVersion.HTTP_1_1)
transfer_encoding = field;
// Do NOT add yet!
break;
}
case CONNECTION: {
if (request != null)
putTo(field, header);
// Lookup and/or split connection value field
HttpHeaderValue[] values = HttpHeaderValue.CLOSE.is(field.getValue()) ? CLOSE
: new HttpHeaderValue[] { HttpHeaderValue.CACHE.get(field.getValue()) };
String[] split = null;
if (values[0] == null) {
split = StringUtils.csvSplit(field.getValue());
if (split.length > 0) {
values = new HttpHeaderValue[split.length];
for (int i = 0; i < split.length; i++)
values[i] = HttpHeaderValue.CACHE.get(split[i]);
}
}
// Handle connection values
for (int i = 0; i < values.length; i++) {
HttpHeaderValue value = values[i];
switch (value == null ? HttpHeaderValue.UNKNOWN : value) {
case UPGRADE: {
// special case for websocket connection
// ordering
header.put(HttpHeader.CONNECTION.getBytesColonSpace())
.put(HttpHeader.UPGRADE.getBytes());
header.put(CRLF);
break;
}
case CLOSE: {
close = true;
_persistent = false;
if (response != null) {
if (_endOfContent == EndOfContent.UNKNOWN_CONTENT)
_endOfContent = EndOfContent.EOF_CONTENT;
}
break;
}
case KEEP_ALIVE: {
if (_info.getVersion() == HttpVersion.HTTP_1_0) {
keep_alive = true;
if (response != null)
_persistent = true;
}
break;
}
default: {
if (connection == null)
connection = new StringBuilder();
else
connection.append(',');
connection.append(split == null ? field.getValue() : split[i]);
}
}
}
// Do NOT add yet!
break;
}
case SERVER: {
send = send & ~SEND_SERVER;
putTo(field, header);
break;
}
default:
putTo(field, header);
}
}
}
}
// Calculate how to end _content and connection, _content length and transfer encoding
// settings.
// From http://tools.ietf.org/html/rfc7230#section-3.3.3
// From RFC 2616 4.4:
// 1. No body for 1xx, 204, 304 & HEAD response
// 3. If Transfer-Encoding==(.*,)?chunked && HTTP/1.1 && !HttpConnection==close then chunk
// 5. Content-Length without Transfer-Encoding
// 6. Request and none over the above, then Content-Length=0 if POST/PUT
// 7. close
int status = response != null ? response.getStatus() : -1;
switch (_endOfContent) {
case UNKNOWN_CONTENT:
// It may be that we have no _content, or perhaps _content just has not been
// written yet?
// Response known not to have a body
if (_contentPrepared == 0 && response != null && _noContent)
_endOfContent = EndOfContent.NO_CONTENT;
else if (_info.getContentLength() > 0) {
// we have been given a content length
_endOfContent = EndOfContent.CONTENT_LENGTH;
if ((response != null || content_length > 0 || content_type) && !_noContent) {
// known length but not actually set.
header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace());
BufferUtils.putDecLong(header, content_length);
header.put(HttpTokens.CRLF);
}
} else if (last) {
// we have seen all the _content there is, so we can be
// content-length limited.
_endOfContent = EndOfContent.CONTENT_LENGTH;
long actual_length = _contentPrepared + BufferUtils.length(content);
if (content_length >= 0 && content_length != actual_length)
throw new BadMessageException(500,
"Content-Length header(" + content_length + ") != actual(" + actual_length + ")");
// Do we need to tell the headers about it
putContentLength(header, actual_length, content_type, request, response);
} else {
// No idea, so we must assume that a body is coming.
_endOfContent = EndOfContent.CHUNKED_CONTENT;
// HTTP 1.0 does not understand chunked content, so we must use
// EOF content.
// For a request with HTTP 1.0 & Connection: keep-alive
// we *must* close the connection, otherwise the client
// has no way to detect the end of the content.
if (!isPersistent() || _info.getVersion().ordinal() < HttpVersion.HTTP_1_1.ordinal())
_endOfContent = EndOfContent.EOF_CONTENT;
}
break;
case CONTENT_LENGTH: {
putContentLength(header, content_length, content_type, request, response);
break;
}
case NO_CONTENT:
throw new BadMessageException(500);
case EOF_CONTENT:
_persistent = request != null;
break;
case CHUNKED_CONTENT:
break;
default:
break;
}
// Add transfer_encoding if needed
if (isChunking()) {
// try to use user supplied encoding as it may have other values.
if (transfer_encoding != null
&& !HttpHeaderValue.CHUNKED.toString().equalsIgnoreCase(transfer_encoding.getValue())) {
String c = transfer_encoding.getValue();
if (c.endsWith(HttpHeaderValue.CHUNKED.toString()))
putTo(transfer_encoding, header);
else
throw new BadMessageException(500, "BAD TE");
} else
header.put(TRANSFER_ENCODING_CHUNKED);
}
// Handle connection if need be
if (_endOfContent == EndOfContent.EOF_CONTENT) {
keep_alive = false;
_persistent = false;
}
// If this is a response, work out persistence
if (response != null) {
if (!isPersistent() && (close || _info.getVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal())) {
if (connection == null)
header.put(CONNECTION_CLOSE);
else {
header.put(CONNECTION_CLOSE, 0, CONNECTION_CLOSE.length - 2);
header.put((byte) ',');
header.put(StringUtils.getBytes(connection.toString()));
header.put(CRLF);
}
} else if (keep_alive) {
if (connection == null)
header.put(CONNECTION_KEEP_ALIVE);
else {
header.put(CONNECTION_KEEP_ALIVE, 0, CONNECTION_KEEP_ALIVE.length - 2);
header.put((byte) ',');
header.put(StringUtils.getBytes(connection.toString()));
header.put(CRLF);
}
} else if (connection != null) {
header.put(HttpHeader.CONNECTION.getBytesColonSpace());
header.put(StringUtils.getBytes(connection.toString()));
header.put(CRLF);
}
}
if (status > 199)
header.put(SEND[send]);
// end the header.
header.put(HttpTokens.CRLF);
}
private void putContentLength(ByteBuffer header, long contentLength, boolean contentType, MetaData.Request request,
MetaData.Response response) {
if (contentLength > 0) {
header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace());
BufferUtils.putDecLong(header, contentLength);
header.put(HttpTokens.CRLF);
} else if (!_noContent) {
if (contentType || response != null
|| (request != null && __assumedContentMethods.contains(request.getMethod())))
header.put(CONTENT_LENGTH_0);
}
}
public static byte[] getReasonBuffer(int code) {
PreparedResponse status = code < __preprepared.length ? __preprepared[code] : null;
if (status != null)
return status._reason;
return null;
}
@Override
public String toString() {
return String.format("%s@%x{s=%s}", getClass().getSimpleName(), hashCode(), _state);
}
// common _content
private static final byte[] LAST_CHUNK = { (byte) '0', (byte) '\015', (byte) '\012', (byte) '\015', (byte) '\012' };
private static final byte[] CONTENT_LENGTH_0 = StringUtils.getBytes("Content-Length: 0\015\012");
private static final byte[] CONNECTION_KEEP_ALIVE = StringUtils.getBytes("Connection: keep-alive\015\012");
private static final byte[] CONNECTION_CLOSE = StringUtils.getBytes("Connection: close\015\012");
private static final byte[] HTTP_1_1_SPACE = StringUtils.getBytes(HttpVersion.HTTP_1_1 + " ");
private static final byte[] CRLF = StringUtils.getBytes("\015\012");
private static final byte[] TRANSFER_ENCODING_CHUNKED = StringUtils.getBytes("Transfer-Encoding: chunked\015\012");
private static final byte[][] SEND = new byte[][] { new byte[0],
StringUtils.getBytes("Server: Firefly 4.0\015\012"),
StringUtils.getBytes("X-Powered-By: Firefly 4.0\015\012"),
StringUtils.getBytes("Server: Firefly 4.0\015\012X-Powered-By: Firefly 4.0\015\012") };
// Build cache of response lines for status
private static class PreparedResponse {
byte[] _reason;
byte[] _schemeCode;
byte[] _responseLine;
}
private static final PreparedResponse[] __preprepared = new PreparedResponse[HttpStatus.MAX_CODE + 1];
static {
int versionLength = HttpVersion.HTTP_1_1.toString().length();
for (int i = 0; i < __preprepared.length; i++) {
HttpStatus.Code code = HttpStatus.getCode(i);
if (code == null)
continue;
String reason = code.getMessage();
byte[] line = new byte[versionLength + 5 + reason.length() + 2];
HttpVersion.HTTP_1_1.toBuffer().get(line, 0, versionLength);
line[versionLength + 0] = ' ';
line[versionLength + 1] = (byte) ('0' + i / 100);
line[versionLength + 2] = (byte) ('0' + (i % 100) / 10);
line[versionLength + 3] = (byte) ('0' + (i % 10));
line[versionLength + 4] = ' ';
for (int j = 0; j < reason.length(); j++)
line[versionLength + 5 + j] = (byte) reason.charAt(j);
line[versionLength + 5 + reason.length()] = HttpTokens.CARRIAGE_RETURN;
line[versionLength + 6 + reason.length()] = HttpTokens.LINE_FEED;
__preprepared[i] = new PreparedResponse();
__preprepared[i]._schemeCode = Arrays.copyOfRange(line, 0, versionLength + 5);
__preprepared[i]._reason = Arrays.copyOfRange(line, versionLength + 5, line.length - 2);
__preprepared[i]._responseLine = line;
}
}
private static void putSanitisedName(String s, ByteBuffer buffer) {
int l = s.length();
for (int i = 0; i < l; i++) {
char c = s.charAt(i);
if (c < 0 || c > 0xff || c == '\r' || c == '\n' || c == ':')
buffer.put((byte) '?');
else
buffer.put((byte) (0xff & c));
}
}
private static void putSanitisedValue(String s, ByteBuffer buffer) {
int l = s.length();
for (int i = 0; i < l; i++) {
char c = s.charAt(i);
if (c < 0 || c > 0xff || c == '\r' || c == '\n')
buffer.put((byte) ' ');
else
buffer.put((byte) (0xff & c));
}
}
public static void putTo(HttpField field, ByteBuffer bufferInFillMode) {
if (field instanceof PreEncodedHttpField) {
((PreEncodedHttpField) field).putTo(bufferInFillMode, HttpVersion.HTTP_1_0);
} else {
HttpHeader header = field.getHeader();
if (header != null) {
bufferInFillMode.put(header.getBytesColonSpace());
putSanitisedValue(field.getValue(), bufferInFillMode);
} else {
putSanitisedName(field.getName(), bufferInFillMode);
bufferInFillMode.put(__colon_space);
putSanitisedValue(field.getValue(), bufferInFillMode);
}
BufferUtils.putCRLF(bufferInFillMode);
}
}
public static void putTo(HttpFields fields, ByteBuffer bufferInFillMode) {
for (HttpField field : fields) {
if (field != null)
putTo(field, bufferInFillMode);
}
BufferUtils.putCRLF(bufferInFillMode);
}
}