HTTPClient.RedirectionModule Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of grinder-httpclient Show documentation
Show all versions of grinder-httpclient Show documentation
Modified version of HTTPClient used by The Grinder. The original
can be found at http://www.innovation.ch/java/HTTPClient/.
The newest version!
/*
* @(#)RedirectionModule.java 0.3-3E 06/05/2001
*
* This file is part of the HTTPClient package
* Copyright (C) 1996-2001 Ronald Tschalär
*
* 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 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
*
* For questions, suggestions, bug-reports, enhancement-requests etc.
* I may be contacted at:
*
* [email protected]
*
* The HTTPClient's home page is located at:
*
* http://www.innovation.ch/java/HTTPClient/
*
* This file contains modifications for use with "The Grinder"
* (http://grinder.sourceforge.net) under the terms of the LGPL. They
* are marked below with the comment "GRINDER MODIFICATION".
*
*/
package HTTPClient;
import java.net.ProtocolException;
import java.io.IOException;
import java.util.Hashtable;
/**
* This module handles the redirection status codes 301, 302, 303, 305, 306
* and 307.
*
* @version 0.3-3E 06/05/2001
* @author Ronald Tschalär
*/
class RedirectionModule implements HTTPClientModule
{
/** a list of permanent redirections (301) */
private static Hashtable perm_redir_cntxt_list = new Hashtable();
/** a list of deferred redirections (used with Response.retryRequest()) */
private static Hashtable deferred_redir_list = new Hashtable();
/** the level of redirection */
private int level;
/** the url used in the last redirection */
private URI lastURI;
/** used for deferred redirection retries */
private boolean new_con;
/** used for deferred redirection retries */
private Request saved_req;
// Constructors
/**
* Start with level 0.
*/
RedirectionModule()
{
level = 0;
lastURI = null;
saved_req = null;
}
// Methods
/**
* Invoked by the HTTPClient.
*/
public int requestHandler(Request req, Response[] resp)
{
HTTPConnection con = req.getConnection();
URI new_loc,
cur_loc;
// check for retries
HttpOutputStream out = req.getStream();
if (out != null && deferred_redir_list.get(out) != null)
{
copyFrom((RedirectionModule) deferred_redir_list.remove(out));
req.copyFrom(saved_req);
if (new_con)
return REQ_NEWCON_RST;
else
return REQ_RESTART;
}
// handle permanent redirections
try
{
cur_loc = new URI(new URI(con.getProtocol(), con.getHost(), con.getPort(), null),
req.getRequestURI());
}
catch (ParseException pe)
{
throw new Error("HTTPClient Internal Error: unexpected exception '"
+ pe + "'");
}
// handle permanent redirections
Hashtable perm_redir_list = Util.getList(perm_redir_cntxt_list,
req.getConnection().getContext());
if ((new_loc = (URI) perm_redir_list.get(cur_loc)) != null)
{
/* copy query if present in old url but not in new url. This
* isn't strictly conforming, but some scripts fail to properly
* propagate the query string to the Location header.
*
* Unfortunately it looks like we're fucked either way: some
* scripts fail if you don't propagate the query string, some
* fail if you do... God, don't you just love it when people
* can't read a spec? Anway, since we can't get it right for
* all scripts we opt to follow the spec.
String nres = new_loc.getPathAndQuery(),
oquery = Util.getQuery(req.getRequestURI()),
nquery = Util.getQuery(nres);
if (nquery == null && oquery != null)
nres += "?" + oquery;
*/
String nres = new_loc.getPathAndQuery();
req.setRequestURI(nres);
try
{ lastURI = new URI(new_loc, nres); }
catch (ParseException pe)
{ }
Log.write(Log.MODS, "RdirM: matched request in permanent " +
"redirection list - redoing request to " +
lastURI.toExternalForm());
if (!con.isCompatibleWith(new_loc))
{
try
{ con = new HTTPConnection(new_loc); }
catch (Exception e)
{
throw new Error("HTTPClient Internal Error: unexpected " +
"exception '" + e + "'");
}
con.setSSLSocketFactory(req.getConnection().getSSLSocketFactory());
con.setContext(req.getConnection().getContext());
req.setConnection(con);
return REQ_NEWCON_RST;
}
else
{
return REQ_RESTART;
}
}
return REQ_CONTINUE;
}
/**
* Invoked by the HTTPClient.
*/
public void responsePhase1Handler(Response resp, RoRequest req)
throws IOException
{
int sts = resp.getStatusCode();
if (sts < 301 || sts > 307 || sts == 304)
{
if (lastURI != null) // it's been redirected
resp.setEffectiveURI(lastURI);
}
}
/**
* Invoked by the HTTPClient.
*/
public int responsePhase2Handler(Response resp, Request req)
throws IOException
{
/* handle various response status codes until satisfied */
int sts = resp.getStatusCode();
switch(sts)
{
case 302: // General (temporary) Redirection (handle like 303)
/* Note we only do this munging for POST and PUT. For GET it's
* not necessary; for HEAD we probably want to do another HEAD.
* For all others (i.e. methods from WebDAV, IPP, etc) it's
* somewhat unclear - servers supporting those should really
* return a 307 or 303, but some don't (guess who...), so we
* just don't touch those.
*/
if (req.getMethod().equals("POST") ||
req.getMethod().equals("PUT"))
{
Log.write(Log.MODS, "RdirM: Received status: " + sts +
" " + resp.getReasonLine() +
" - treating as 303");
sts = 303;
}
case 301: // Moved Permanently
case 303: // See Other (use GET)
case 307: // Moved Temporarily (we mean it!)
Log.write(Log.MODS, "RdirM: Handling status: " + sts +
" " + resp.getReasonLine());
// the spec says automatic redirection may only be done if
// the second request is a HEAD or GET.
if (!req.getMethod().equals("GET") &&
!req.getMethod().equals("HEAD") &&
sts != 303)
{
Log.write(Log.MODS, "RdirM: not redirected because " +
"method is neither HEAD nor GET");
if (sts == 301 && resp.getHeader("Location") != null)
update_perm_redir_list(req,
resLocHdr(resp.getHeader("Location"), req));
resp.setEffectiveURI(lastURI);
return RSP_CONTINUE;
}
case 305: // Use Proxy
case 306: // Switch Proxy
if (sts == 305 || sts == 306)
Log.write(Log.MODS, "RdirM: Handling status: " + sts +
" " + resp.getReasonLine());
// Don't accept 305 from a proxy
if (sts == 305 && req.getConnection().getProxyHost() != null)
{
Log.write(Log.MODS, "RdirM: 305 ignored because " +
"a proxy is already in use");
resp.setEffectiveURI(lastURI);
return RSP_CONTINUE;
}
/* the level is a primitive way of preventing infinite
* redirections. RFC-2068 set the max to 5, but RFC-2616
* has loosened this. Since some sites (notably M$) need
* more levels, this is now set to the (arbitrary) value
* of 15 (god only knows why they need to do even 5
* redirections...).
*/
if (level >= 15 || resp.getHeader("Location") == null)
{
if (level >= 15)
Log.write(Log.MODS, "RdirM: not redirected because "+
"of too many levels of redirection");
else
Log.write(Log.MODS, "RdirM: not redirected because "+
"no Location header was present");
resp.setEffectiveURI(lastURI);
return RSP_CONTINUE;
}
level++;
URI loc = resLocHdr(resp.getHeader("Location"), req);
HTTPConnection mvd;
String nres;
new_con = false;
if (sts == 305)
{
mvd = new HTTPConnection(req.getConnection().getProtocol(),
req.getConnection().getHost(),
req.getConnection().getPort());
mvd.setCurrentProxy(loc.getHost(), loc.getPort());
mvd.setSSLSocketFactory(req.getConnection().getSSLSocketFactory());
mvd.setContext(req.getConnection().getContext());
new_con = true;
nres = req.getRequestURI();
/* There was some discussion about this, and especially
* Foteos Macrides (Lynx) said a 305 should also imply
* a change to GET (for security reasons) - see the thread
* starting at
* http://www.ics.uci.edu/pub/ietf/http/hypermail/1997q4/0351.html
* However, this is not in the latest draft, but since I
* agree with Foteos we do it anyway...
*/
req.setMethod("GET");
req.setData(null);
req.setStream(null);
}
else if (sts == 306)
{
// We'll have to wait for Josh to create a new spec here.
return RSP_CONTINUE;
}
else
{
if (req.getConnection().isCompatibleWith(loc))
{
mvd = req.getConnection();
nres = loc.getPathAndQuery();
}
else
{
try
{
mvd = new HTTPConnection(loc);
nres = loc.getPathAndQuery();
}
catch (Exception e)
{
if (req.getConnection().getProxyHost() == null ||
!loc.getScheme().equalsIgnoreCase("ftp"))
return RSP_CONTINUE;
// We're using a proxy and the protocol is ftp -
// maybe the proxy will also proxy ftp...
mvd = new HTTPConnection("http",
req.getConnection().getProxyHost(),
req.getConnection().getProxyPort());
mvd.setCurrentProxy(null, 0);
nres = loc.toExternalForm();
}
mvd.setSSLSocketFactory(req.getConnection().getSSLSocketFactory());
mvd.setContext(req.getConnection().getContext());
/* GRINDER MODIFICATION++ */
mvd.setCheckCertificates(
req.getConnection().getCheckCertificates());
mvd.setTestConnectionHealthWithBlockingRead(
req.getConnection().getTestConnectionHealthWithBlockingRead());
mvd.setTimeAuthority(req.getConnection().getTimeAuthority());
/* --GRINDER MODIFICATION */
new_con = true;
}
/* copy query if present in old url but not in new url.
* This isn't strictly conforming, but some scripts fail
* to propagate the query properly to the Location
* header.
*
* See comment on line 126.
String oquery = Util.getQuery(req.getRequestURI()),
nquery = Util.getQuery(nres);
if (nquery == null && oquery != null)
nres += "?" + oquery;
*/
if (sts == 303)
{
// 303 means "use GET"
if (!req.getMethod().equals("HEAD"))
req.setMethod("GET");
req.setData(null);
req.setStream(null);
}
else
{
// If they used an output stream then they'll have
// to do the resend themselves
if (req.getStream() != null)
{
if (!HTTPConnection.deferStreamed)
{
Log.write(Log.MODS, "RdirM: status " + sts +
" not handled - request " +
"has an output stream");
return RSP_CONTINUE;
}
saved_req = (Request) req.clone();
deferred_redir_list.put(req.getStream(), this);
req.getStream().reset();
resp.setRetryRequest(true);
}
if (sts == 301)
{
// update permanent redirection list
try
{
update_perm_redir_list(req, new URI(loc, nres));
}
catch (ParseException pe)
{
throw new Error("HTTPClient Internal Error: " +
"unexpected exception '" + pe +
"'");
}
}
}
// Adjust Referer, if present
NVPair[] hdrs = req.getHeaders();
for (int idx=0; idx