package io.craft.atom.protocol.http;
import static io.craft.atom.protocol.http.HttpConstants.CR;
import static io.craft.atom.protocol.http.HttpConstants.HT;
import static io.craft.atom.protocol.http.HttpConstants.LF;
import static io.craft.atom.protocol.http.HttpConstants.SP;
import io.craft.atom.protocol.ProtocolDecoder;
import io.craft.atom.protocol.ProtocolException;
import io.craft.atom.protocol.http.model.HttpMethod;
import io.craft.atom.protocol.http.model.HttpRequest;
import io.craft.atom.protocol.http.model.HttpVersion;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import lombok.ToString;
/**
* A {@link ProtocolDecoder} which decodes bytes into {@code HttpRequest} object, default charset is utf-8.
* <br>
* Not thread safe.
*
* @author mindwind
* @version 1.0, Feb 2, 2013
*/
@ToString(callSuper = true)
public class HttpRequestDecoder extends HttpDecoder<HttpRequest> implements ProtocolDecoder<HttpRequest> {
public HttpRequestDecoder() {
super();
}
public HttpRequestDecoder(Charset charset) {
this.charset = charset;
}
public HttpRequestDecoder(Charset charset, int defaultBufferSize) {
this(charset);
this.defaultBufferSize = defaultBufferSize;
buf.reset(defaultBufferSize);
}
public HttpRequestDecoder(Charset charset, int defaultBufferSize, int maxLineLength) {
this(charset, defaultBufferSize);
this.maxLineLength = maxLineLength;
}
public HttpRequestDecoder(Charset charset, int defaultBufferSize, int maxLineLength, int maxRequestSize) {
this(charset, defaultBufferSize, maxLineLength);
this.maxSize = maxRequestSize;
}
// ~ ------------------------------------------------------------------------------------------------------------
@Override
public List<HttpRequest> decode(byte[] bytes) throws ProtocolException {
try {
return decode0(bytes);
} catch (Exception e) {
clear();
resetIndex();
if (e instanceof ProtocolException) {
throw (ProtocolException) e;
}
throw new ProtocolException(e);
}
}
protected boolean hasEntity(HttpRequest request) {
HttpMethod method = request.getRequestLine().getMethod();
if (method == HttpMethod.POST || method == HttpMethod.PUT) {
// Only POST and PUT method may has entity in a http request.
return true;
}
return false;
}
private List<HttpRequest> decode0(byte[] bytes) throws ProtocolException, IOException {
List<HttpRequest> reqs = new ArrayList<HttpRequest>();
adapt();
buf.append(bytes);
while (searchIndex < buf.length()) {
switch (state) {
case START:
state4START();
break;
case METHOD:
state4METHOD();
break;
case REQUEST_URI:
state4REQUEST_URI();
break;
case VERSION:
state4VERSION();
break;
case HEADER_NAME:
state4HEADER_NAME();
break;
case HEADER_VALUE_PREFIX:
state4HEADER_VALUE_PREFIX();
break;
case HEADER_VALUE:
state4HEADER_VALUE();
break;
case HEADER_VALUE_SUFFIX:
state4HEADER_VALUE_SUFFIX();
break;
case ENTITY:
state4ENTITY();
break;
case ENTITY_LENGTH:
state4ENTITY_LENGTH();
break;
case ENTITY_CHUNKED_SIZE:
state4ENTITY_CHUNKED_SIZE();
break;
case ENTITY_CHUNKED_EXTENSION_NAME:
state4ENTITY_CHUNKED_EXTENSION_NAME();
break;
case ENTITY_CHUNKED_EXTENSION_VALUE:
state4ENTITY_CHUNKED_EXTENSION_VALUE();
break;
case ENTITY_CHUNKED_DATA:
state4ENTITY_CHUNKED_DATA();
break;
case ENTITY_CHUNKED_TRAILER_NAME:
state4ENTITY_CHUNKED_TRAILER_NAME();
break;
case ENTITY_CHUNKED_TRAILER_VALUE:
state4ENTITY_CHUNKED_TRAILER_VALUE();
break;
case ENTITY_ENCODING:
state4ENTITY_ENCODING();
break;
case END:
state4END(reqs);
break;
default:
throw new IllegalStateException("Invalid decoder state!");
}
}
return reqs;
}
private void state4VERSION() throws ProtocolException {
// slice version part
String versionStr = sliceBySeparators(-1, LF);
if (versionStr == null) {
return;
}
// render current request with version
HttpVersion version = HttpVersion.from(versionStr);
httpMessage.getRequestLine().setVersion(version);
// to next state;
if (CR == currentByte() && LF == nextByte()) {
state = ENTITY;
slide(2);
} else {
state = HEADER_NAME;
}
}
private void state4REQUEST_URI() throws ProtocolException {
// slice request uri part
String uri = sliceBySeparators(0, SP, HT);
if (uri == null) {
return;
}
// render current request with request uri
httpMessage.getRequestLine().setUri(uri);
// to next state
state = VERSION;
}
private void state4METHOD() throws ProtocolException {
// slice method part
String methodStr = sliceBySeparators(0, SP, HT);
if (methodStr == null) {
return;
}
// render current request with method
HttpMethod method = HttpMethod.valueOf(methodStr);
httpMessage.getRequestLine().setMethod(method);
// to next state
state = REQUEST_URI;
}
private void state4START() throws ProtocolException {
// skip any CR or LF before request line
boolean done = skip(CR, LF);
if (!done) {
return;
}
// on START state create a new request as current request.
httpMessage = new HttpRequest();
// to next state
state = METHOD;
}
}