/**
* Copyright 2005-2014 Restlet
*
* The contents of this file are subject to the terms of one of the following
* open source licenses: Apache 2.0 or or EPL 1.0 (the "Licenses"). You can
* select the license that you prefer but you may not use this file except in
* compliance with one of these Licenses.
*
* You can obtain a copy of the Apache 2.0 license at
* http://www.opensource.org/licenses/apache-2.0
*
* You can obtain a copy of the EPL 1.0 license at
* http://www.opensource.org/licenses/eclipse-1.0
*
* See the Licenses for the specific language governing permissions and
* limitations under the Licenses.
*
* Alternatively, you can obtain a royalty free commercial license with less
* limitations, transferable or non-transferable, directly at
* http://restlet.com/products/restlet-framework
*
* Restlet is a registered trademark of Restlet S.A.S.
*/
package org.restlet.routing;
import java.util.logging.Level;
import org.restlet.Application;
import org.restlet.Component;
import org.restlet.Context;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.Restlet;
import org.restlet.data.Reference;
import org.restlet.data.Status;
import org.restlet.engine.header.HeaderConstants;
import org.restlet.engine.header.HeaderUtils;
import org.restlet.representation.Representation;
import org.restlet.util.Resolver;
/**
* Rewrites URIs then redirects the call or the client to a new destination.
* There are various redirection modes that you can choose from: client-side
* redirections ({@link #MODE_CLIENT_FOUND}, {@link #MODE_CLIENT_PERMANENT},
* {@link #MODE_CLIENT_SEE_OTHER}, {@link #MODE_CLIENT_TEMPORARY}) or
* server-side redirections, similar to a reverse proxy (
* {@link #MODE_SERVER_OUTBOUND} and {@link #MODE_SERVER_INBOUND}).<br>
* <br>
* When setting the redirection URIs, you can also used special URI variables to
* reuse most properties from the original request as well as URI template
* variables. For a complete list of properties, please see the {@link Resolver}
* class. For example "/target?referer={fi}" would redirect to the relative URI,
* inserting the referrer URI as a query parameter.<br>
* <br>
* To create a reverse proxy, a typically configuration will use the
* {@link #MODE_SERVER_OUTBOUND} constant and a target URI like
* "http://targetHost/targetRootPath/{rr}" to ensure that all child URIs are
* properly redirected as well, "rr" appending the remaining part of the current
* request URI that hasn't been routed yet.<br>
* <br>
* Concurrency note: instances of this class or its subclasses can be invoked by
* several threads at the same time and therefore must be thread-safe. You
* should be especially careful when storing state in member variables.
*
* @see org.restlet.routing.Template
* @see <a href="http://wiki.restlet.org/docs_2.2/375-restlet.html">User Guide -
* URI rewriting and redirection</a>
* @author Jerome Louvel
*/
public class Redirector extends Restlet {
/**
* In this mode, the client is simply redirected to the URI generated from
* the target URI pattern using the {@link Status#REDIRECTION_FOUND} status.
* Note: this is a client-side redirection.<br>
*
* @see Status#REDIRECTION_FOUND
*/
public static final int MODE_CLIENT_FOUND = 2;
/**
* In this mode, the client is permanently redirected to the URI generated
* from the target URI pattern, using the
* {@link Status#REDIRECTION_PERMANENT} status. Note: this is a client-side
* redirection.<br>
*
* @see Status#REDIRECTION_PERMANENT
*/
public static final int MODE_CLIENT_PERMANENT = 1;
/**
* In this mode, the client is simply redirected to the URI generated from
* the target URI pattern using the {@link Status#REDIRECTION_SEE_OTHER}
* status. Note: this is a client-side redirection.<br>
*
* @see Status#REDIRECTION_SEE_OTHER
*/
public static final int MODE_CLIENT_SEE_OTHER = 3;
/**
* In this mode, the client is temporarily redirected to the URI generated
* from the target URI pattern using the
* {@link Status#REDIRECTION_TEMPORARY} status. Note: this is a client-side
* redirection.<br>
*
* @see Status#REDIRECTION_TEMPORARY
*/
public static final int MODE_CLIENT_TEMPORARY = 4;
/**
* In this mode, the call is sent to {@link Context#getServerDispatcher()}.
* Once the selected client connector has completed the request handling,
* the response is normally returned to the client. In this case, you can
* view the Redirector as acting as a transparent proxy Restlet. Note: this
* is a server-side redirection.<br>
* <br>
* Warning: remember to add the required connectors to the parent
* {@link Component} and to declare them in the list of required connectors
* on the {@link Application#getConnectorService()} property.<br>
* <br>
* Note that in this mode, the headers of HTTP requests, stored in the
* request's attributes, are removed before dispatching. Also, when a HTTP
* response comes back the headers are also removed. You can control this
* behavior by setting the {@link #headersCleaning} attribute or by
* overriding the {@link #rewrite(Request)} or {@link #rewrite(Response)}.
*
* @see Context#getServerDispatcher()
*/
public static final int MODE_SERVER_INBOUND = 7;
/**
* In this mode, the call is sent to {@link Application#getOutboundRoot()}
* or if null to {@link Context#getClientDispatcher()}. Once the selected
* client connector has completed the request handling, the response is
* normally returned to the client. In this case, you can view the
* {@link Redirector} as acting as a transparent server-side proxy. Note:
* this is a server-side redirection.<br>
* <br>
* Warning: remember to add the required connectors to the parent
* {@link Component} and to declare them in the list of required connectors
* on the {@link Application#getConnectorService()} property.<br>
* <br>
* Note that in this mode, the headers of HTTP requests, stored in the
* request's attributes, are removed before dispatching. Also, when a HTTP
* response comes back the headers are also removed. You can control this
* behavior by setting the {@link #headersCleaning} attribute or by
* overriding the {@link #rewrite(Request)} or {@link #rewrite(Response)}.
*
* @see Application#getOutboundRoot()
* @see Context#getClientDispatcher()
*/
public static final int MODE_SERVER_OUTBOUND = 6;
/**
* Indicates if the headers of HTTP requests stored in the request's
* attributes, and the .
*/
protected volatile boolean headersCleaning;
/** The redirection mode. */
protected volatile int mode;
/** The target URI pattern. */
protected volatile String targetTemplate;
/**
* Constructor for the client dispatcher mode.
*
* @param context
* The context.
* @param targetTemplate
* The template to build the target URI.
* @see org.restlet.routing.Template
*/
public Redirector(Context context, String targetTemplate) {
this(context, targetTemplate, MODE_SERVER_OUTBOUND);
}
/**
* Constructor.
*
* @param context
* The context.
* @param targetPattern
* The pattern to build the target URI (using StringTemplate
* syntax and the CallModel for variables).
* @param mode
* The redirection mode.
*/
public Redirector(Context context, String targetPattern, int mode) {
super(context);
this.targetTemplate = targetPattern;
this.mode = mode;
this.headersCleaning = true;
}
/**
* Computes the new location of the given reference, after applying the
* redirection template. Returns null in case it cannot compute the new
* reference.
*
* @param locationRef
* The reference to translate.
* @param request
* The current request.
* @return The new location of the given reference.
*/
private String getLocation(Reference locationRef, Request request) {
Reference resourceRef = request.getResourceRef();
Reference baseRef = resourceRef.getBaseRef();
Template rt = new Template(this.targetTemplate);
rt.setLogger(getLogger());
int matched = rt.parse(locationRef.toString(), request);
if (matched > 0) {
String remainingPart = (String) request.getAttributes().get("rr");
if (remainingPart != null) {
return baseRef.toString() + remainingPart;
}
}
return null;
}
/**
* Returns the redirection mode.
*
* @return The redirection mode.
*/
public int getMode() {
return this.mode;
}
/**
* Returns the target reference to redirect to by automatically resolving
* URI template variables found using the {@link Template} class using the
* request and response as data models.
*
* @param request
* The request to handle.
* @param response
* The response to update.
* @return The target reference to redirect to.
*/
protected Reference getTargetRef(Request request, Response response) {
// Create the template
Template rt = new Template(this.targetTemplate);
rt.setLogger(getLogger());
// Return the formatted target URI
if (new Reference(this.targetTemplate).isRelative()) {
// Be sure to keep the resource's base reference.
return new Reference(request.getResourceRef(), rt.format(request,
response));
}
return new Reference(rt.format(request, response));
}
/**
* Returns the target URI pattern.
*
* @return The target URI pattern.
*/
public String getTargetTemplate() {
return this.targetTemplate;
}
/**
* Handles a call by redirecting using the selected redirection mode.
*
* @param request
* The request to handle.
* @param response
* The response to update.
*/
@Override
public void handle(Request request, Response response) {
// Generate the target reference
Reference targetRef = getTargetRef(request, response);
switch (this.mode) {
case MODE_CLIENT_PERMANENT:
if (request.isLoggable()) {
getLogger().log(Level.FINE,
"Permanently redirecting client to: " + targetRef);
}
response.redirectPermanent(targetRef);
break;
case MODE_CLIENT_FOUND:
if (request.isLoggable()) {
getLogger().log(Level.FINE,
"Redirecting client to found location: " + targetRef);
}
response.setLocationRef(targetRef);
response.setStatus(Status.REDIRECTION_FOUND);
break;
case MODE_CLIENT_SEE_OTHER:
if (request.isLoggable()) {
getLogger().log(Level.FINE,
"Redirecting client to another location: " + targetRef);
}
response.redirectSeeOther(targetRef);
break;
case MODE_CLIENT_TEMPORARY:
if (request.isLoggable()) {
getLogger().log(Level.FINE,
"Temporarily redirecting client to: " + targetRef);
}
response.redirectTemporary(targetRef);
break;
case MODE_SERVER_OUTBOUND:
if (request.isLoggable()) {
getLogger().log(Level.FINE,
"Redirecting via client dispatcher to: " + targetRef);
}
outboundServerRedirect(targetRef, request, response);
break;
case MODE_SERVER_INBOUND:
if (request.isLoggable()) {
getLogger().log(Level.FINE,
"Redirecting via server dispatcher to: " + targetRef);
}
inboundServerRedirect(targetRef, request, response);
break;
}
}
/**
* Redirects a given call to a target reference. In the default
* implementation, the request HTTP headers, stored in the request's
* attributes, are removed before dispatching. After dispatching, the
* response HTTP headers are also removed to prevent conflicts with the main
* call.
*
* @param targetRef
* The target reference with URI variables resolved.
* @param request
* The request to handle.
* @param response
* The response to update.
*/
protected void inboundServerRedirect(Reference targetRef, Request request,
Response response) {
serverRedirect(getContext().getServerDispatcher(), targetRef, request,
response);
}
/**
* Indicates if the headers must be cleaned.
*
* @return True if the headers must be cleaned.
*/
public boolean isHeadersCleaning() {
return headersCleaning;
}
/**
* Redirects a given call to a target reference. In the default
* implementation, the request HTTP headers, stored in the request's
* attributes, are removed before dispatching. After dispatching, the
* response HTTP headers are also removed to prevent conflicts with the main
* call.
*
* @param targetRef
* The target reference with URI variables resolved.
* @param request
* The request to handle.
* @param response
* The response to update.
*/
protected void outboundServerRedirect(Reference targetRef, Request request,
Response response) {
Restlet next = (getApplication() == null) ? null : getApplication()
.getOutboundRoot();
if (next == null) {
next = getContext().getClientDispatcher();
}
serverRedirect(next, targetRef, request, response);
if (response.getEntity() != null
&& !request.getResourceRef().getScheme()
.equalsIgnoreCase(targetRef.getScheme())) {
// Distinct protocol, this data cannot be exposed.
response.getEntity().setLocationRef((Reference) null);
}
}
/**
* Optionally rewrites the response entity returned in the
* {@link #MODE_SERVER_INBOUND} and {@link #MODE_SERVER_OUTBOUND} modes. By
* default, it just returns the initial entity without any modification.
*
* @param initialEntity
* The initial entity returned.
* @return The rewritten entity.
*/
protected Representation rewrite(Representation initialEntity) {
return initialEntity;
}
/**
* Optionally updates the request sent in the {@link #MODE_SERVER_INBOUND}
* and {@link #MODE_SERVER_OUTBOUND} modes. By default, it leverages the
* {@link #headersCleaning} attribute in order to clean the headers: if set
* to true, it removes all headers, otherwise it keeps only the extension
* (or non HTTP standard) headers<br>
*
* @param initialRequest
* The initial request returned.
* @return The updated request.
*/
protected void rewrite(Request initialRequest) {
if (isHeadersCleaning()) {
initialRequest.getAttributes().remove(
HeaderConstants.ATTRIBUTE_HEADERS);
} else {
HeaderUtils.keepExtensionHeadersOnly(initialRequest);
}
}
/**
* Optionally updates the response sent in the {@link #MODE_SERVER_INBOUND}
* and {@link #MODE_SERVER_OUTBOUND} modes. By default, it leverages the
* {@link #headersCleaning} attribute in order to clean the headers: if set
* to true, it removes all headers, otherwise it keeps only the extension
* (or non HTTP standard) headers<br>
*
* @param initialRequest
* The initial request returned.
* @return The updated request.
*/
protected void rewrite(Response initialResponse) {
if (isHeadersCleaning()) {
initialResponse.getAttributes().remove(
HeaderConstants.ATTRIBUTE_HEADERS);
} else {
HeaderUtils.keepExtensionHeadersOnly(initialResponse);
}
}
/**
* Rewrite the location of the response, and the Location of the entity, if
* any.
*
* @param request
* The request to handle.
* @param response
* The response to update.
*/
public void rewriteLocation(Request request, Response response) {
if (response.getLocationRef() != null) {
Reference locationRef = response.getLocationRef();
String newLocation = getLocation(locationRef, request);
if (newLocation != null) {
response.setLocationRef(newLocation);
}
}
if (response.getEntity() != null
&& response.getEntity().getLocationRef() != null) {
Reference locationRef = response.getEntity().getLocationRef();
String newLocation = getLocation(locationRef, request);
if (newLocation != null) {
response.getEntity().setLocationRef(newLocation);
}
}
}
/**
* Redirects a given call on the server-side to a next Restlet with a given
* target reference. In the default implementation, the request HTTP
* headers, stored in the request's attributes, are removed before
* dispatching. After dispatching, the response HTTP headers are also
* removed to prevent conflicts with the main call.
*
* @param next
* The next Restlet to forward the call to.
* @param targetRef
* The target reference with URI variables resolved.
* @param request
* The request to handle.
* @param response
* The response to update.
*/
protected void serverRedirect(Restlet next, Reference targetRef,
Request request, Response response) {
if (next == null) {
getLogger().warning(
"No next Restlet provided for server redirection to "
+ targetRef);
} else {
// Save the base URI if it exists as we might need it for
// redirections
Reference resourceRef = request.getResourceRef();
// Reset the protocol and let the dispatcher handle the protocol
request.setProtocol(null);
// Update the request to cleanly go to the target URI
request.setResourceRef(targetRef);
rewrite(request);
next.handle(request, response);
request.setResourceRef(resourceRef);
// Allow for response rewriting and clean the headers
response.setEntity(rewrite(response.getEntity()));
rewrite(response);
// In case of redirection, we may have to rewrite the redirect URI
rewriteLocation(request, response);
}
}
/**
* Indicates if the headers must be cleaned.
*
* @param headersCleaning
* True if the headers must be cleaned.
*/
public void setHeadersCleaning(boolean headersCleaning) {
this.headersCleaning = headersCleaning;
}
/**
* Sets the redirection mode.
*
* @param mode
* The redirection mode.
*/
public void setMode(int mode) {
this.mode = mode;
}
/**
* Sets the target URI pattern.
*
* @param targetTemplate
* The target URI pattern.
*/
public void setTargetTemplate(String targetTemplate) {
this.targetTemplate = targetTemplate;
}
}