package io.craft.atom.protocol.http.model;
import static io.craft.atom.protocol.http.HttpConstants.S_AMPERSAND;
import static io.craft.atom.protocol.http.HttpConstants.S_Q_MARK;
import io.craft.atom.protocol.ProtocolException;
import io.craft.atom.protocol.http.HttpCookieDecoder;
import io.craft.atom.protocol.http.HttpHeaders;
import io.craft.atom.protocol.http.HttpParameterDecoder;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A request message from a client to a server includes, within the
* first line of that message, the method to be applied to the resource,
* the identifier of the resource, and the protocol version in use.
* <pre>
* Request = Request-Line
* *(( general-header
* | request-header
* | entity-header ) CRLF)
* CRLF
* [ message-body ]
* </pre>
*
* @author mindwind
* @version 1.0, Feb 1, 2013
*/
@ToString(callSuper = true, of = { "requestLine", "parameterMap" })
public class HttpRequest extends HttpMessage {
private static final long serialVersionUID = 2454619732646455653L ;
private static final Logger LOG = LoggerFactory.getLogger(HttpRequest.class);
private static final HttpCookieDecoder COOKIE_DECODER = new HttpCookieDecoder() ;
private static final HttpParameterDecoder PARAMETER_DECODER = new HttpParameterDecoder() ;
@Getter @Setter private HttpRequestLine requestLine = new HttpRequestLine();
@Setter private Map<String, List<String>> parameterMap ;
// ~ ------------------------------------------------------------------------------------------------------------
public HttpRequest() {
super();
}
public HttpRequest(HttpRequestLine requestLine) {
this.requestLine = requestLine;
}
public HttpRequest(HttpRequestLine requestLine, List<HttpHeader> headers) {
super(headers);
this.requestLine = requestLine;
}
public HttpRequest(HttpRequestLine requestLine, List<HttpHeader> headers, HttpEntity entity) {
super(headers, entity);
this.requestLine = requestLine;
}
// ~ ------------------------------------------------------------------------------------------------------------
/**
* Returns the value of a request parameter as a <code>String</code>, or
* <code>null</code> if the parameter does not exist. Request parameters are
* extra information sent with the request. For HTTP, parameters are contained in the query string
* or posted form data with content type <code>application/x-www-form-urlencoded</code>.
* <p>
* You should only use this method when you are sure the parameter has only one value.
* If the parameter might have more than one value, use {@link #getParameters}.
*
* @param name
* @return parameter value.
*/
public String getParameter(String name) {
if (name == null) {
return null;
}
List<String> values = getParameters(name);
if (values.isEmpty()) {
return null;
}
return values.get(0);
}
/**
* Returns an array of <code>String</code> objects containing all of the
* values the given request parameter has, or empty list if the parameter does not exist.
*
* @param name
* @return parameter value list
*/
public List<String> getParameters(String name) {
List<String> values = new ArrayList<String>();
if (name == null) {
return values;
}
Map<String, List<String>> map = getParameterMap();
values = map.get(name);
if (values == null) {
values = Collections.emptyList();
}
return values;
}
/**
* @return a immutable parameter map of this request
*/
public Map<String, List<String>> getParameterMap() {
if (this.parameterMap != null) {
return Collections.unmodifiableMap(this.parameterMap);
}
synchronized (this) {
if (this.parameterMap != null) {
return Collections.unmodifiableMap(this.parameterMap);
}
return Collections.unmodifiableMap(parseParameters());
}
}
private Map<String, List<String>> parseParameters() {
Map<String, List<String>> map = Collections.emptyMap();
StringBuilder buf = new StringBuilder();
String queryString = getQueryString();
if (queryString != null) {
buf.append(queryString);
}
HttpContentType contentType = getContentType();
if (contentType != null && MimeType.APPLICATION_X_WWW_FORM_URLENCODED == contentType.getMimeType()) {
buf.append(S_AMPERSAND).append(getEntity().getContentAsString());
}
try {
List<Map<String, List<String>>> paras = PARAMETER_DECODER.decode(buf.toString().getBytes(PARAMETER_DECODER.getCharset()));
if (!paras.isEmpty()) {
map = paras.get(0);
}
} catch (ProtocolException e) {
LOG.warn("[CRAFT-ATOM-PROTOCOL-HTTP] Decode request parameter error", e);
return map;
}
this.parameterMap = map;
return this.parameterMap;
}
/**
* Get the query string that is contained in the request URL after
* the path. This method returns <code>null</code> if the URL does not have a query string.
*
* @return query string
*/
public String getQueryString() {
if (requestLine == null) {
return null;
}
String uri = requestLine.getUri();
if (uri == null) {
return null;
}
int idx = uri.indexOf(S_Q_MARK);
if (idx < 0) {
return null;
}
return uri.substring(idx + 1);
}
@Override
public void addCookie(HttpCookie cookie) {
addHeader(HttpHeaders.newCookieHeader(cookie));
}
@Override
protected List<HttpCookie> parseCookies() {
List<HttpCookie> cookies = new ArrayList<HttpCookie>();
List<HttpHeader> cookieHeaders = getHeaders(HttpHeaderType.COOKIE.getName());
for (HttpHeader cookieHeader : cookieHeaders) {
String cookieValue = cookieHeader.getValue();
try {
List<HttpCookie> cl = COOKIE_DECODER.decode(cookieValue.getBytes(COOKIE_DECODER.getCharset()));
cookies.addAll(cl);
} catch (ProtocolException e) {
LOG.warn("[CRAFT-ATOM-PROTOCOL-HTTP] Decode request cookie error", e);
return cookies;
}
}
this.cookies = cookies;
return this.cookies;
}
// ~ ------------------------------------------------------------------------------------------------------------
public String toHttpString(Charset charset) {
StringBuilder sb = new StringBuilder();
// request line
HttpRequestLine requestLine = getRequestLine();
if (requestLine != null) {
sb.append(requestLine.toHttpString());
}
// message headers and entity
sb.append(super.toHttpString(charset));
return sb.toString();
}
}