/*
* Copyright (C) 2015 SoftIndex LLC.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.datakernel.http;
import io.datakernel.bytebuf.ByteBuf;
import io.datakernel.exception.ParseException;
import java.util.*;
import static io.datakernel.bytebuf.ByteBufStrings.*;
import static io.datakernel.http.HttpHeaders.CONTENT_TYPE;
import static io.datakernel.http.HttpHeaders.DATE;
/**
* Represents any HTTP message. Its internal byte buffers will be automatically recycled in HTTP client or HTTP server.
*/
public abstract class HttpMessage {
protected boolean recycled;
final ArrayList<HttpHeaders.Value> headers = new ArrayList<>();
private ArrayList<ByteBuf> headerBufs;
protected ByteBuf body;
protected Boolean useGzip;
protected HttpMessage() {
}
public final Map<HttpHeader, String> getHeaders() {
LinkedHashMap<HttpHeader, String> map = new LinkedHashMap<>(headers.size() * 2);
for (HttpHeaders.Value headerValue : headers) {
HttpHeader header = headerValue.getKey();
String headerString = headerValue.toString();
if (!map.containsKey(header)) {
map.put(header, headerString);
}
}
return map;
}
public final Map<HttpHeader, List<String>> getAllHeaders() {
LinkedHashMap<HttpHeader, List<String>> map = new LinkedHashMap<>(headers.size() * 2);
for (HttpHeaders.Value headerValue : headers) {
HttpHeader header = headerValue.getKey();
String headerString = headerValue.toString();
List<String> strings = map.get(header);
if (strings == null) {
strings = new ArrayList<>();
map.put(header, strings);
}
strings.add(headerString);
}
return map;
}
/**
* Sets the header with value to this HttpMessage.
* Checks whether the header was already applied to the message.
*
* @param value value of this header
*/
protected void setHeader(HttpHeaders.Value value) {
assert !recycled;
assert getHeaderValue(value.getKey()) == null : "Duplicate header: " + value.getKey();
headers.add(value);
}
/**
* Adds the header with value to this HttpMessage
* Does not check whether the header was already applied to the message.
*
* @param value value of this header
*/
protected void addHeader(HttpHeaders.Value value) {
assert !recycled;
headers.add(value);
}
public void addHeader(HttpHeader header, ByteBuf value) {
assert !recycled;
addHeader(HttpHeaders.asBytes(header, value.array(), value.readPosition(), value.readRemaining()));
if (value.isRecycleNeeded()) {
if (headerBufs == null) {
headerBufs = new ArrayList<>(4);
}
headerBufs.add(value);
}
}
public void addHeader(HttpHeader header, byte[] value) {
assert !recycled;
addHeader(HttpHeaders.asBytes(header, value, 0, value.length));
}
public void addHeader(HttpHeader header, String string) {
assert !recycled;
addHeader(HttpHeaders.ofString(header, string));
}
public void setBody(ByteBuf body) {
assert !recycled;
if (this.body != null)
this.body.recycle();
this.body = body;
}
public void setBody(byte[] body) {
assert !recycled;
this.body = ByteBuf.wrapForReading(body);
}
public void setGzipCompression(boolean allow) {
// we are not setting header as it still could be prohibited to use compression on the server level
this.useGzip = allow ? Boolean.TRUE : Boolean.FALSE;
}
// getters
public ContentType getContentType() {
assert !recycled;
HttpHeaders.ValueOfBytes header = (HttpHeaders.ValueOfBytes) getHeaderValue(CONTENT_TYPE);
if (header != null) {
try {
return ContentType.parse(header.array, header.offset, header.size);
} catch (ParseException e) {
return null;
}
}
return null;
}
public Date getDate() {
assert !recycled;
HttpHeaders.ValueOfBytes header = (HttpHeaders.ValueOfBytes) getHeaderValue(DATE);
if (header != null) {
try {
long date = HttpDate.parse(header.array, header.offset);
return new Date(date);
} catch (ParseException e) {
return null;
}
}
return null;
}
/**
* Removes the body of this message and returns it. After its method, owner of
* body of this HttpMessage is changed, and it will not be automatically recycled in HTTP client or HTTP server.
*
* @return the body
*/
public ByteBuf detachBody() {
ByteBuf buf = body;
body = null;
return buf;
}
public ByteBuf getBody() {
assert !recycled;
return body;
}
/**
* Recycles body and header. You should do it before reusing.
*/
protected void recycleBufs() {
assert !recycled;
if (body != null) {
body.recycle();
body = null;
}
if (headerBufs != null) {
for (ByteBuf headerBuf : headerBufs) {
headerBuf.recycle();
}
headerBufs = null;
}
recycled = true;
}
/**
* Sets headers for this message from ByteBuf
*
* @param buf the new headers
*/
protected void writeHeaders(ByteBuf buf) {
assert !recycled;
for (HttpHeaders.Value entry : this.headers) {
HttpHeader header = entry.getKey();
buf.put(CR);
buf.put(LF);
header.writeTo(buf);
buf.put((byte) ':');
buf.put(SP);
entry.writeTo(buf);
}
buf.put(CR);
buf.put(LF);
buf.put(CR);
buf.put(LF);
}
protected void writeBody(ByteBuf buf) {
assert !recycled;
if (body != null) {
buf.put(body);
}
}
protected int estimateSize(int firstLineSize) {
assert !recycled;
int size = firstLineSize;
for (HttpHeaders.Value entry : this.headers) {
HttpHeader header = entry.getKey();
size += 2 + header.size() + 2 + entry.estimateSize(); // CR,LF,header,": ",value
}
size += 4; // CR,LF,CR,LF
if (body != null)
size += body.readRemaining();
return size;
}
protected final HttpHeaders.Value getHeaderValue(HttpHeader header) {
for (HttpHeaders.Value headerValue : headers) {
if (header.equals(headerValue.getKey()))
return headerValue;
}
return null;
}
public final String getHeader(HttpHeader header) {
HttpHeaders.Value result = getHeaderValue(header);
return result == null ? null : result.toString();
}
protected final List<HttpHeaders.Value> getHeaderValues(HttpHeader header) {
List<HttpHeaders.Value> result = new ArrayList<>();
for (HttpHeaders.Value headerValue : headers) {
if (header.equals(headerValue.getKey()))
result.add(headerValue);
}
return result;
}
protected abstract List<HttpCookie> getCookies();
public Map<String, HttpCookie> getCookiesMap() {
assert !recycled;
List<HttpCookie> cookies = getCookies();
LinkedHashMap<String, HttpCookie> map = new LinkedHashMap<>();
for (HttpCookie cookie : cookies) {
map.put(cookie.getName(), cookie);
}
return map;
}
public HttpCookie getCookie(String name) {
assert !recycled;
List<HttpCookie> cookies = getCookies();
for (HttpCookie cookie : cookies) {
if (name.equals(cookie.getName()))
return cookie;
}
return null;
}
public abstract ByteBuf toByteBuf();
}