package org.httpkit.client;
import org.httpkit.*;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.ExecutorService;
import java.util.zip.GZIPInputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
import static org.httpkit.HttpUtils.CONTENT_ENCODING;
import static org.httpkit.HttpUtils.CONTENT_TYPE;
class Handler implements Runnable {
private final int status;
private final Map<String, Object> headers;
private final Object body;
private final IResponseHandler handler;
public Handler(IResponseHandler handler, int status, Map<String, Object> headers,
Object body) {
this.handler = handler;
this.status = status;
this.headers = headers;
this.body = body;
}
public Handler(IResponseHandler handler, Throwable e) {
this(handler, 0, null, e);
}
public void run() {
try {
if (body instanceof Throwable) {
handler.onThrowable((Throwable) body);
} else {
handler.onSuccess(status, headers, body);
}
} catch (Exception e) { // onSuccess may throw Exception
handler.onThrowable(e); // should not throw exception
}
}
}
/**
* Accumulate all the response, call upper logic at once, for easy use
*/
public class RespListener implements IRespListener {
private boolean isText() {
if (status.getCode() == 200) {
String type = HttpUtils.getStringValue(headers, CONTENT_TYPE);
if (type != null) {
type = type.toLowerCase();
// TODO may miss something
return type.contains("text") || type.contains("json") || type.contains("xml");
} else {
return false;
}
} else { // non 200: treat as text
return true;
}
}
private DynamicBytes unzipBody() throws IOException {
String encoding = HttpUtils.getStringValue(headers, CONTENT_ENCODING);
if (encoding == null || body.length() == 0) {
return body;
}
encoding = encoding.toLowerCase();
BytesInputStream bis = new BytesInputStream(body.get(), body.length());
InputStream is;
if ("gzip".equals(encoding) || "x-gzip".equals(encoding)) {
is = new GZIPInputStream(bis);
} else if ("deflate".equals(encoding) || "x-deflate".equals(encoding)) {
// http://stackoverflow.com/questions/3932117/handling-http-contentencoding-deflate
is = new InflaterInputStream(bis, new Inflater(true));
} else {
return body; // not compressed
}
DynamicBytes unzipped = new DynamicBytes(body.length() * 5);
byte[] buffer = new byte[4096];
int read;
while ((read = is.read(buffer)) != -1) {
unzipped.append(buffer, read);
}
is.close();
return unzipped;
}
private final DynamicBytes body;
// can be empty
private Map<String, Object> headers = new TreeMap<String, Object>();
private HttpStatus status;
private final IResponseHandler handler;
private final IFilter filter;
private final ExecutorService pool;
final int coercion;
public RespListener(IResponseHandler handler, IFilter filter, ExecutorService pool, int coercion) {
body = new DynamicBytes(1024 * 8);
this.filter = filter;
this.handler = handler;
this.coercion = coercion;
this.pool = pool;
}
public void onBodyReceived(byte[] buf, int length) throws AbortException {
body.append(buf, length);
if (filter != null && !filter.accept(body)) {
throw new AbortException("Rejected when reading body, length: " + body.length());
}
}
public void onCompleted() {
if (status == null) {
pool.submit(new Handler(handler, new ProtocolException("No status")));
return;
}
try {
DynamicBytes bytes = unzipBody();
// 1=> auto, 2=>text, 3=>stream, 4=>byte-array
if (coercion == 2 || (coercion == 1 && isText())) {
Charset charset = HttpUtils.detectCharset(headers, bytes);
String html = new String(bytes.get(), 0, bytes.length(), charset);
pool.submit(new Handler(handler, status.getCode(), headers, html));
} else {
BytesInputStream is = new BytesInputStream(bytes.get(), bytes.length());
if (coercion == 4) { // byte-array
pool.submit(new Handler(handler, status.getCode(), headers, is.bytes()));
} else {
pool.submit(new Handler(handler, status.getCode(), headers, is));
}
}
} catch (IOException e) {
handler.onThrowable(e);
}
}
public void onThrowable(Throwable t) {
pool.submit(new Handler(handler, t));
}
public void onHeadersReceived(Map<String, Object> headers) throws AbortException {
this.headers = headers;
if (filter != null && !filter.accept(headers)) {
throw new AbortException("Rejected when header received");
}
}
public void onInitialLineReceived(HttpVersion version, HttpStatus status)
throws AbortException {
this.status = status;
}
}