/*
* Copyright (c) xlightweb.org, 2008 - 2009. All rights reserved.
*
* 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.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Please refer to the LGPL license at: http://www.gnu.org/copyleft/lesser.txt
* The latest copy of this software may be found on http://www.xlightweb.org/
*/
package org.xlightweb.client;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.xlightweb.HttpUtils;
import org.xlightweb.IHttpExchange;
import org.xlightweb.IHttpRequest;
import org.xlightweb.IHttpRequestHandler;
import org.xlightweb.IHttpRequestHeader;
import org.xlightweb.IHttpResponse;
import org.xlightweb.IHttpResponseHandler;
import org.xlightweb.IHttpResponseHeader;
import org.xlightweb.InvokeOn;
import org.xsocket.Execution;
import org.xsocket.ILifeCycle;
/**
* Auto redirect handler
*
* @author grro@xlightweb.org
*/
final class AutoRedirectHandler implements IHttpRequestHandler, ILifeCycle {
private static final Logger LOG = Logger.getLogger(AutoRedirectHandler.class.getName());
private HttpClient httpClient = null;
/**
* constructor
*
* @param httpClient the http client to perform the redirects
*/
public AutoRedirectHandler(HttpClient httpClient) {
this.httpClient = httpClient;
}
public void onInit() {
}
public void onDestroy() throws IOException {
httpClient = null;
}
/**
* {@inheritDoc}
*/
@InvokeOn(InvokeOn.MESSAGE_RECEIVED)
@Execution(Execution.NONTHREADED)
public void onRequest(IHttpExchange exchange) throws IOException {
IHttpRequest request = exchange.getRequest();
exchange.forward(request, new RedirectedHandler(HttpUtils.copy(request), exchange));
}
private final class RedirectedHandler implements IHttpResponseHandler {
private IHttpRequest request = null;
private IHttpExchange exchange = null;
private Integer countRedirects = 0;
RedirectedHandler(IHttpRequest request, IHttpExchange exchange) throws IOException {
this.request = request;
this.exchange = exchange;
countRedirects = (Integer) request.getAttribute("org.xlightweb.client.AutoRedirectHandler.countRedirects");
if (countRedirects == null) {
countRedirects = 0;
}
}
@Execution(Execution.NONTHREADED)
public void onResponse(IHttpResponse response) throws IOException {
if (isRedirectResponse(request.getRequestHeader(), response.getResponseHeader())) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("got redirect response: " + response.getResponseHeader());
}
// get the new location
URL newLocation = getRedirectURI(response, request.isSecure(), request.getServerName(), request.getServerPort());
// .. and update the stored request
request.setRequestUrl(newLocation);
if (countRedirects < httpClient.getMaxRedirects()) {
// if is secured perform redirect request in threaded context -> implicit startSSL() has to be performed within threaded context
if (request.isSecure()) {
Runnable task = new Runnable() {
public void run() {
sendRedirectedRequest();
}
};
httpClient.getWorkerpool().execute(task);
} else {
sendRedirectedRequest();
}
} else {
exchange.sendError(new IOException("max redirects " + httpClient.getMaxRedirects() + " reached. request will not be executed: " + request.getRequestHeader()));
}
} else {
exchange.send(response);
}
}
private void sendRedirectedRequest() {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("send redirected request: " + request.getRequestHeader());
}
try {
request.setAttribute("org.xlightweb.client.AutoRedirectHandler.countRedirects", ++countRedirects);
IHttpResponseHandler respHdl = new IHttpResponseHandler() {
@Execution(Execution.NONTHREADED)
public void onResponse(IHttpResponse response) throws IOException {
exchange.send(response);
}
@Execution(Execution.NONTHREADED)
public void onException(IOException ioe) throws IOException {
exchange.sendError(ioe);
}
};
httpClient.send(request, respHdl);
} catch (IOException ioe) {
exchange.sendError(new IOException("can execute redirect request " + request.getRequestHeader() + " reason: " + ioe.toString()));
}
}
@Execution(Execution.NONTHREADED)
public void onException(final IOException ioe) {
exchange.sendError(ioe);
}
private boolean isRedirectResponse(IHttpRequestHeader requestHeader, IHttpResponseHeader responseHeader) {
switch (responseHeader.getStatus()) {
// 300 Multiple choices
case 300:
return false;
// 301 Moved permanently
case 301:
if (requestHeader.getMethod().equalsIgnoreCase("GET") || requestHeader.getMethod().equalsIgnoreCase("HEAD")) {
return true;
}
return false;
// 302 found
case 302:
if (httpClient.isTreat302RedirectAs303()) {
return true;
}
if (requestHeader.getMethod().equalsIgnoreCase("GET") || requestHeader.getMethod().equalsIgnoreCase("HEAD")) {
return true;
}
return false;
// 303 See other
case 303:
return true;
// 304 Not modified
case 304:
return false;
// 305 Use proxy
case 305:
return false;
// 306 (unused)
case 306:
return false;
// 307 temporary redirect
case 307:
return false;
default:
return false;
}
}
private URL getRedirectURI(IHttpResponse response, boolean isSSL, String originalHost, int originalPort) {
String location = response.getHeader("Location");
if (location != null) {
try {
if (isRelativeUrl(location)) {
if (isSSL) {
if (originalPort == -1) {
return new URL("https://" + originalHost + location);
} else {
return new URL("https://" + originalHost + ":" + originalPort + location);
}
} else {
if (originalPort == -1) {
return new URL("http://" + originalHost + location);
} else {
return new URL("http://" + originalHost + ":" + originalPort + location);
}
}
} else {
return new URL(location);
}
} catch (MalformedURLException e) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("could not create relocation url . reason " + e.toString());
}
}
}
return null;
}
}
private boolean isRelativeUrl(String url) {
url = url.toUpperCase();
return (!url.startsWith("HTTP") && !url.startsWith("HTTPS"));
}
}