/*
* JLibs: Common Utilities for Java
* Copyright (C) 2009 Santhosh Kumar T <santhosh.tekuri@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package jlibs.nio.http;
import jlibs.nio.*;
import jlibs.nio.filters.CloseTrackingInput;
import jlibs.nio.filters.TrackingInput;
import jlibs.nio.http.msg.Method;
import jlibs.nio.http.msg.Request;
import jlibs.nio.http.msg.Response;
import jlibs.nio.http.msg.Status;
import jlibs.nio.http.msg.parser.ResponseParser;
import jlibs.nio.http.util.Expect;
import jlibs.nio.listeners.IOListener;
import java.util.Collection;
import java.util.Iterator;
import static java.nio.channels.SelectionKey.OP_WRITE;
import static jlibs.nio.Debugger.HTTP;
import static jlibs.nio.Debugger.println;
import static jlibs.nio.http.ClientExchange.State.*;
import static jlibs.nio.http.msg.Method.HEAD;
/**
* @author Santhosh Kumar Tekuri
*/
public class ClientExchange extends Exchange{
private Collection<ClientFilter> requestFilters;
private Collection<ClientFilter> responseFilters;
private final HTTPClient client;
private TCPEndpoint endpoint;
private TCPEndpoint retry;
private ResponseListener user;
private AccessLog accessLog;
private AccessLog.Record accessLogRecord;
protected ClientExchange(HTTPClient client, TCPEndpoint endpoint){
super(client.maxResponseHeadSize, new ResponseParser(), OP_WRITE);
this.client = client;
this.endpoint = endpoint;
requestFilters= client.requestFilters;
responseFilters = client.responseFilters;
accessLog = client.accessLog;
if(accessLog!=null){
accessLogRecord = accessLog.records.allocate();
accessLogRecord.setLogHandler(client.logHandler);
}
}
enum State{
PREPARE_REQUEST_FILTERS, FILTER_REQUEST, WRITE_REQUEST,
READ_RESPONSE, SEND_REQUEST_PAYLOAD, PREPARE_RESPONSE_FILTERS, FILTER_RESPONSE,
DELIVER_RESPONSE, DRAIN_RESPONSE, PREPARE_COMPLETE, COMPLETE,
COMPLETED, CLOSED
}
private State state = PREPARE_REQUEST_FILTERS;
private Method requestMethod;
private boolean responseHasPayload;
private boolean continue100Expected;
private boolean trackClose = false;
protected Iterator<ClientFilter> filters;
@Override
protected boolean process(int readyOp){
while(true){
try{
switch(state){
case PREPARE_REQUEST_FILTERS:
if(in!=null)
in.setInputListener(null);
filters = requestFilters.iterator();
state = FILTER_REQUEST;
if(HTTP)
println("state = "+state);
case FILTER_REQUEST:
while(filters.hasNext()){
if(!filters.next().filter(this, FilterType.REQUEST))
return false;
}
if(in==null){
endpoint.getConnection(this::connectCompleted, client.proxy);
return false;
}
state = WRITE_REQUEST;
if(HTTP)
println("state = "+state);
case WRITE_REQUEST:
in.setInputListener(listener);
if(client.keepAliveTimeout<0)
keepAlive = request.isKeepAlive();
else
request.setKeepAlive(keepAlive=client.keepAliveTimeout!=0);
requestMethod = request.method;
String hostPort = endpoint.port==80 || endpoint.port==443 ? endpoint.host : endpoint.toString();
request.headers.set(Request.HOST, hostPort);
if(client.userAgent !=null)
request.setUserAgent(client.userAgent);
continue100Expected = false;
if(request.version.expectSupported && request.getPayload().getContentLength()!=0){
if(request.getExpectation()==Expect.CONTINUE_100)
continue100Expected = true;
}
writeMessage.reset(request, null, !continue100Expected);
if(accessLog!=null)
accessLogRecord.process(this, request);
setChild(writeMessage);
return true;
case READ_RESPONSE:
response = new Response();
readMessage.reset(response, requestMethod==HEAD);
setChild(readMessage);
return true;
case PREPARE_RESPONSE_FILTERS:
in.setInputListener(null);
filters = responseFilters.iterator();
state = FILTER_RESPONSE;
if(HTTP)
println("state = "+state);
case FILTER_RESPONSE:
while(filters.hasNext()){
if(!filters.next().filter(this, FilterType.RESPONSE))
return false;
if(retry!=null)
break;
}
state = DELIVER_RESPONSE;
if(HTTP)
println("state = "+state);
case DELIVER_RESPONSE:
if(retry==null)
user.process(this, error);
state = COMPLETE;
if(in!=null){
if(responseHasPayload){
if(retry==null){
if(in.isOpen()){
trackClose = true;
return false;
}else if(!in.eof())
keepAlive = false;
}else if(keepAlive && !retry.equals(endpoint))
keepAlive = false;
}
in = ((jlibs.nio.Readable)in.channel()).in();
in.setInputListener(listener);
}else
assert !keepAlive;
if(HTTP)
println("state = "+state);
case COMPLETE:
if(keepAlive && responseHasPayload){
if(!drainInputs())
return false;
}
if(keepAlive){
if(retry==null)
Reactor.current().connectionPool.add(endpoint.toString(), (Connection)in.channel(), Math.abs(client.keepAliveTimeout));
}else
close();
if(retry==null)
notifyCallback();
if(retry!=null){
if(retry.equals(endpoint) && in!=null)
Reactor.current().connectionPool.remove((Connection)in.channel());
else{
in = null;
out = null;
}
endpoint = retry;
retry = null;
state = PREPARE_REQUEST_FILTERS;
requestMethod = null;
responseHasPayload = false;
continue100Expected = false;
trackClose = false;
error = null;
response = null;
if(HTTP)
println("state = "+state);
break;
}else
return true;
case CLOSED:
return true;
case SEND_REQUEST_PAYLOAD:
setChild(writeMessage);
return true;
}
}catch(Throwable thr){
setError(thr);
}
}
}
public void setAccessLog(ServerExchange exchange){
accessLog = exchange.accessLog;
accessLogRecord = exchange.accessLogRecord;
}
public void execute(ResponseListener listener){
if(HTTP)
println(this+".execute{");
user = listener;
assert state==PREPARE_REQUEST_FILTERS;
process(OP_WRITE);
if(HTTP)
println("}");
}
private void connectCompleted(Result<Connection> result){
try{
Connection con = result.get();
connectionStatus = ConnectionStatus.OPEN;
state = WRITE_REQUEST;
if(HTTP)
println("state = "+state);
new IOListener().start(this, con);
}catch(Throwable thr){
setError(thr);
process(0);
}
}
@Override
protected void writeMessageFinished(Throwable thr){
if(thr==null){
state = READ_RESPONSE;
if(HTTP)
println("state = "+state);
}else
setError(thr);
}
@Override
protected void readMessageFinished(Throwable thr){
if(accessLog!=null){
try{
accessLogRecord.process(this, response);
}catch(Throwable thr1){
Reactor.current().handleException(thr1);
}
}
if(thr==null){
if(continue100Expected && Status.CONTINUE.equals(response.status))
state = SEND_REQUEST_PAYLOAD;
else{
responseHasPayload = response.getPayload().getContentLength()!=0;
if(responseHasPayload){
SocketPayload socketPayload = (SocketPayload)response.getPayload();
in = socketPayload.in = new CloseTrackingInput(in, this::responsePayloadClosed);
}
if(keepAlive)
keepAlive = readMessage.keepAlive();
state = PREPARE_RESPONSE_FILTERS;
}
if(HTTP)
println("state = "+state);
}else
setError(thr);
}
private void responsePayloadClosed(TrackingInput trackingInput){
if(trackClose){
assert retry==null;
if(!in.eof())
keepAlive = false;
in = ((jlibs.nio.Readable)in.channel()).in();
if(callback==null)
listener.process(in);
else{
in.setInputListener(listener);
System.err.println("wakeupreader");
in.wakeupReader();
}
}
}
@Override
protected void reset(){
super.reset();
}
public void setRequest(Request request){
this.request = request;
}
protected void setError(Throwable thr){
error = thr;
if(HTTP)
println("error = "+error);
keepAlive = false;
retry = null;
if(state.ordinal()<DELIVER_RESPONSE.ordinal())
state = DELIVER_RESPONSE;
else
state = COMPLETED ;
if(HTTP)
println("state = "+state);
}
protected ClientCallback callback;
public void setCallback(ClientCallback callback){
this.callback = callback;
}
@Trace(condition=HTTP)
private void notifyCallback(){
try{
if(accessLog!=null)
accessLogRecord.finished(this);
}catch(Throwable thr){
Reactor.current().handleException(thr);
}
if(callback!=null){
try{
callback.completed(this, error);
}catch(Throwable unexpected){
Reactor.current().handleException(unexpected);
}
callback = null;
}else if(error!=null)
Reactor.current().handleException(error);
}
@Override
public void close(){
super.close();
state = CLOSED;
}
@Override
public TCPEndpoint getEndpoint(){
return endpoint;
}
@Override
public Connection stealConnection(){
if(in==null)
return null;
if(HTTP)
println("stealConnection()");
Connection con = (Connection)in.channel();
Reactor.current().connectionPool.remove(con);
in = null;
out = null;
return con;
}
public void retry(){
retry = endpoint;
if(HTTP)
println("retry()");
}
public void retry(TCPEndpoint endpoint){
retry = endpoint;
if(HTTP)
println("retry("+retry+")");
}
@Override
public String toString(){
String str = super.toString();
return str.substring(0, str.length()-1)+":"+state+"]";
}
}