/*
* This file is part of the Wayback archival access software
* (http://archive-access.sourceforge.net/projects/wayback/).
*
* Licensed to the Internet Archive (IA) by one or more individual
* contributors.
*
* The IA licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.archive.wayback.replay;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.archive.wayback.ReplayRenderer;
import org.archive.wayback.ResultURIConverter;
import org.archive.wayback.core.CaptureSearchResult;
import org.archive.wayback.core.CaptureSearchResults;
import org.archive.wayback.core.Resource;
import org.archive.wayback.core.WaybackRequest;
import org.archive.wayback.exception.SpecificCaptureReplayException;
/**
* ReplayRenderer implementation which returns the archive document as
* pristinely as possible -- no modifications to response code, HTTP headers,
* or original byte-stream.
*
* <p>
* Now this class has a limited support for range requests. If the request
* has {@code Range} header, it tries to fulfill it by extracting requested
* byte range from the archived content (either 200 or 206 responses).
* If the Resource is not suitable for replaying the request ranges,
* {@code renderResource} throws {@link RangeNotSatisfiable}, so that
* replay process can try another capture, or take other actions.
* </p>
*
* @author brad
* @version $Date$, $Revision$
*/
public class TransparentReplayRenderer implements ReplayRenderer {
private HttpHeaderProcessor httpHeaderProcessor;
// TODO: Figure out best way to generalize this, but probably good default
// Add special don't cache header in case of at least 100M
private final static long NOCACHE_THRESHOLD = 100000000L;
private final static String NOCACHE_HEADER_NAME = "X-Accel-Buffering";
private final static String NOCACHE_HEADER_VALUE = "no";
private final static int BUFFER_SIZE = 4096;
public TransparentReplayRenderer(HttpHeaderProcessor httpHeaderProcessor) {
this.httpHeaderProcessor = httpHeaderProcessor;
}
@Override
public void renderResource(HttpServletRequest httpRequest,
HttpServletResponse httpResponse, WaybackRequest wbRequest,
CaptureSearchResult result, Resource resource,
ResultURIConverter uriConverter, CaptureSearchResults results)
throws ServletException, IOException,
SpecificCaptureReplayException {
String rangeHeader = httpRequest.getHeader(HttpHeaderOperation.HTTP_RANGE_HEADER_UP);
if (rangeHeader != null) {
long[][] ranges = HttpHeaderOperation.parseRanges(rangeHeader);
@SuppressWarnings("resource")
RangeResource rangeResource = new RangeResource(resource,
ranges);
rangeResource.parseRange();
resource = rangeResource;
}
HttpHeaderOperation.copyHTTPMessageHeader(resource, httpResponse);
// Note: httpHeaderProcessor may remove Content-Length.
Map<String, String> headers = HttpHeaderOperation.processHeaders(
resource, result, uriConverter, httpHeaderProcessor);
String origLength = HttpHeaderOperation.getContentLength(resource.getHttpHeaders());
if (origLength != null) {
HttpHeaderOperation.replaceHeader(headers, HttpHeaderOperation.HTTP_LENGTH_HEADER, origLength);
long contentLength = -1;
try {
contentLength = Long.parseLong(origLength);
} catch (NumberFormatException n) {
}
//TODO: Generalize? Don't buffer NOCACHE_THRESHOLD
if ((contentLength >= NOCACHE_THRESHOLD)) {
headers.put(NOCACHE_HEADER_NAME, NOCACHE_HEADER_VALUE);
}
}
HttpHeaderOperation.sendHeaders(headers, httpResponse);
// and copy the raw byte-stream.
OutputStream os = httpResponse.getOutputStream();
byte[] buffer = new byte[BUFFER_SIZE];
long total = 0;
for (int r = -1; (r = resource.read(buffer, 0, BUFFER_SIZE)) != -1;) {
os.write(buffer, 0, r);
total += r;
}
if (total == 0) {
if (headers.size() == 0) {
// totally empty response
httpResponse.setContentLength(0);
}
}
}
@Override
public void renderResource(HttpServletRequest httpRequest,
HttpServletResponse httpResponse, WaybackRequest wbRequest,
CaptureSearchResult result, Resource httpHeadersResource,
Resource payloadResource, ResultURIConverter uriConverter,
CaptureSearchResults results) throws ServletException, IOException,
SpecificCaptureReplayException {
if (httpHeadersResource != payloadResource) {
httpHeadersResource = new CompositeResource(
httpHeadersResource, payloadResource);
}
renderResource(httpRequest, httpResponse, wbRequest, result,
httpHeadersResource, uriConverter, results);
}
}