blob: 1b379189ef76f834632acab0891e1df9947de15e [file] [log] [blame]
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.servlet;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.LocalConnector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.toolchain.test.FS;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.toolchain.test.OS;
import org.eclipse.jetty.toolchain.test.TestingDir;
import org.eclipse.jetty.util.IO;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
public class DefaultServletRangesTest
{
public static final String DATA = "01234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWZYZ!@#$%^&*()_+/.,[]";
@Rule
public TestingDir testdir = new TestingDir();
private Server server;
private LocalConnector connector;
private ServletContextHandler context;
@Before
public void init() throws Exception
{
server = new Server();
connector = new LocalConnector(server);
connector.getConnectionFactory(HttpConfiguration.ConnectionFactory.class).getHttpConfiguration().setSendServerVersion(false);
context = new ServletContextHandler();
context.setContextPath("/context");
context.setWelcomeFiles(new String[]{"index.html", "index.jsp", "index.htm"});
server.setHandler(context);
server.addConnector(connector);
testdir.ensureEmpty();
File resBase = testdir.getFile("docroot");
FS.ensureDirExists(resBase);
File data = new File(resBase, "data.txt");
createFile(data, DATA);
String resBasePath = resBase.getAbsolutePath();
ServletHolder defholder = context.addServlet(DefaultServlet.class, "/");
defholder.setInitParameter("acceptRanges", "true");
defholder.setInitParameter("resourceBase", resBasePath);
server.start();
}
@After
public void destroy() throws Exception
{
server.stop();
server.join();
}
@Test
public void testNoRangeRequests() throws Exception
{
String response;
response= connector.getResponses(
"GET /context/data.txt HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Connection: close\r\n"+
"\r\n");
assertResponseContains("200 OK", response);
assertResponseContains("Accept-Ranges: bytes", response);
assertResponseContains(DATA,response);
}
@Test
public void testPrefixRangeRequests() throws Exception
{
String response;
response = connector.getResponses(
"GET /context/data.txt HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Connection: close\r\n"+
"Range: bytes=0-9\r\n" +
"\r\n");
assertResponseContains("206 Partial", response);
assertResponseContains("Content-Type: text/plain", response);
assertResponseContains("Content-Length: 10", response);
assertResponseContains("Content-Range: bytes 0-9/80", response);
assertResponseContains(DATA.substring(0,10), response);
}
@Test
public void testSingleRangeRequests() throws Exception
{
String response;
response = connector.getResponses(
"GET /context/data.txt HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Connection: close\r\n"+
"Range: bytes=3-9\r\n" +
"\r\n");
assertResponseContains("206 Partial", response);
assertResponseContains("Content-Type: text/plain", response);
assertResponseContains("Content-Length: 7", response);
assertResponseContains("Content-Range: bytes 3-9/80", response);
assertResponseContains(DATA.substring(3,10), response);
}
@Test
public void testMultipleRangeRequests() throws Exception
{
String response;
response = connector.getResponses(
"GET /context/data.txt HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Connection: close\r\n"+
"Range: bytes=0-9,20-29,40-49\r\n" +
"\r\n");
int start = response.indexOf("--jetty");
String body = response.substring(start);
String boundary = body.substring(0, body.indexOf("\r\n"));
assertResponseContains("206 Partial", response);
assertResponseContains("Content-Type: multipart/byteranges; boundary=", response);
assertResponseContains("Content-Range: bytes 0-9/80", response);
assertResponseContains("Content-Range: bytes 20-29/80", response);
assertResponseContains("Content-Range: bytes 40-49/80", response);
assertResponseContains("Content-Length: " + body.length(), response);
assertResponseContains(DATA.substring(0,10), response);
assertResponseContains(DATA.substring(20,30), response);
assertResponseContains(DATA.substring(40,50), response);
assertTrue(body.endsWith(boundary + "--\r\n"));
}
@Test
public void testOpenEndRange() throws Exception
{
String response;
response = connector.getResponses(
"GET /context/data.txt HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Connection: close\r\n"+
"Range: bytes=20-\r\n" +
"\r\n");
assertResponseContains("206 Partial", response);
assertResponseNotContains("Content-Type: multipart/byteranges; boundary=", response);
assertResponseContains("Content-Range: bytes 20-79/80", response);
assertResponseContains("Content-Length: 60", response);
assertResponseContains(DATA.substring(60), response);
}
@Test
public void testOpenStartRange() throws Exception
{
String response;
response = connector.getResponses(
"GET /context/data.txt HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Connection: close\r\n"+
"Range: bytes=-20\r\n" +
"\r\n");
assertResponseContains("206 Partial", response);
assertResponseNotContains("Content-Type: multipart/byteranges; boundary=", response);
assertResponseContains("Content-Range: bytes 60-79/80", response); // yes the spec says it is these bytes
assertResponseContains("Content-Length: 20", response);
assertResponseContains(DATA.substring(60), response);
}
@Test
public void testUnsatisfiableRanges() throws Exception
{
String response;
response = connector.getResponses(
"GET /context/data.txt HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Connection: close\r\n"+
"Range: bytes=100-110\r\n" +
"\r\n");
assertResponseContains("416 Requested Range Not Satisfiable", response);
}
private void createFile(File file, String str) throws IOException
{
FileOutputStream out = null;
try
{
out = new FileOutputStream(file);
out.write(str.getBytes(StandardCharsets.UTF_8));
out.flush();
}
finally
{
IO.close(out);
}
}
private void assertResponseNotContains(String forbidden, String response)
{
Assert.assertThat(response,Matchers.not(Matchers.containsString(forbidden)));
}
private int assertResponseContains(String expected, String response)
{
Assert.assertThat(response,Matchers.containsString(expected));
return response.indexOf(expected);
}
private void deleteFile(File file) throws IOException
{
if (OS.IS_WINDOWS)
{
// Windows doesn't seem to like to delete content that was recently created
// Attempt a delete and if it fails, attempt a rename
boolean deleted = file.delete();
if (!deleted)
{
File deletedDir = MavenTestingUtils.getTargetFile(".deleted");
FS.ensureDirExists(deletedDir);
File dest = File.createTempFile(file.getName(), "deleted", deletedDir);
boolean renamed = file.renameTo(dest);
if (!renamed)
System.err.println("WARNING: unable to move file out of the way: " + file.getName());
}
}
else
{
Assert.assertTrue("Deleting: " + file.getName(), file.delete());
}
}
}