blob: 8e93b470f4a6fc33c4a4d0c58de7756b07c7a55f [file] [log] [blame]
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed 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 com.squareup.okhttp.internal.spdy;
import com.squareup.okhttp.Protocol;
import com.squareup.okhttp.internal.Platform;
import com.squareup.okhttp.internal.SslContextBuilder;
import com.squareup.okhttp.internal.Util;
import java.io.File;
import java.io.IOException;
import java.net.ProtocolException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import okio.BufferedSink;
import okio.Okio;
import okio.Source;
/** A basic SPDY/HTTP_2 server that serves the contents of a local directory. */
public final class SpdyServer implements IncomingStreamHandler {
static final Logger logger = Logger.getLogger(SpdyServer.class.getName());
private final List<Protocol> spdyProtocols = Util.immutableList(Protocol.HTTP_2, Protocol.SPDY_3);
private final File baseDirectory;
private final SSLSocketFactory sslSocketFactory;
public SpdyServer(File baseDirectory, SSLSocketFactory sslSocketFactory) {
this.baseDirectory = baseDirectory;
this.sslSocketFactory = sslSocketFactory;
}
private void run() throws Exception {
ServerSocket serverSocket = new ServerSocket(8888);
serverSocket.setReuseAddress(true);
while (true) {
Socket socket = null;
try {
socket = serverSocket.accept();
SSLSocket sslSocket = doSsl(socket);
String protocolString = Platform.get().getSelectedProtocol(sslSocket);
Protocol protocol = protocolString != null ? Protocol.get(protocolString) : null;
if (protocol == null || !spdyProtocols.contains(protocol)) {
throw new ProtocolException("Protocol " + protocol + " unsupported");
}
SpdyConnection spdyConnection = new SpdyConnection.Builder(false, sslSocket)
.protocol(protocol)
.handler(this)
.build();
spdyConnection.sendConnectionPreface();
} catch (IOException e) {
logger.log(Level.INFO, "SpdyServer connection failure: " + e);
Util.closeQuietly(socket);
} catch (Exception e) {
logger.log(Level.WARNING, "SpdyServer unexpected failure", e);
Util.closeQuietly(socket);
}
}
}
private SSLSocket doSsl(Socket socket) throws IOException {
SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(
socket, socket.getInetAddress().getHostAddress(), socket.getPort(), true);
sslSocket.setUseClientMode(false);
Platform.get().configureTlsExtensions(sslSocket, null, spdyProtocols);
sslSocket.startHandshake();
return sslSocket;
}
@Override public void receive(final SpdyStream stream) throws IOException {
try {
List<Header> requestHeaders = stream.getRequestHeaders();
String path = null;
for (int i = 0, size = requestHeaders.size(); i < size; i++) {
if (requestHeaders.get(i).name.equals(Header.TARGET_PATH)) {
path = requestHeaders.get(i).value.utf8();
break;
}
}
if (path == null) {
// TODO: send bad request error
throw new AssertionError();
}
File file = new File(baseDirectory + path);
if (file.isDirectory()) {
serveDirectory(stream, file.listFiles());
} else if (file.exists()) {
serveFile(stream, file);
} else {
send404(stream, path);
}
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
private void send404(SpdyStream stream, String path) throws IOException {
List<Header> responseHeaders = Arrays.asList(
new Header(":status", "404"),
new Header(":version", "HTTP/1.1"),
new Header("content-type", "text/plain")
);
stream.reply(responseHeaders, true);
BufferedSink out = Okio.buffer(stream.getSink());
out.writeUtf8("Not found: " + path);
out.close();
}
private void serveDirectory(SpdyStream stream, File[] files) throws IOException {
List<Header> responseHeaders = Arrays.asList(
new Header(":status", "200"),
new Header(":version", "HTTP/1.1"),
new Header("content-type", "text/html; charset=UTF-8")
);
stream.reply(responseHeaders, true);
BufferedSink out = Okio.buffer(stream.getSink());
for (File file : files) {
String target = file.isDirectory() ? (file.getName() + "/") : file.getName();
out.writeUtf8("<a href='" + target + "'>" + target + "</a><br>");
}
out.close();
}
private void serveFile(SpdyStream stream, File file) throws IOException {
List<Header> responseHeaders = Arrays.asList(
new Header(":status", "200"),
new Header(":version", "HTTP/1.1"),
new Header("content-type", contentType(file))
);
stream.reply(responseHeaders, true);
Source source = Okio.source(file);
try {
BufferedSink out = Okio.buffer(stream.getSink());
out.writeAll(source);
out.close();
} finally {
Util.closeQuietly(source);
}
}
private String contentType(File file) {
if (file.getName().endsWith(".css")) return "text/css";
if (file.getName().endsWith(".gif")) return "image/gif";
if (file.getName().endsWith(".html")) return "text/html";
if (file.getName().endsWith(".jpeg")) return "image/jpeg";
if (file.getName().endsWith(".jpg")) return "image/jpeg";
if (file.getName().endsWith(".js")) return "application/javascript";
if (file.getName().endsWith(".png")) return "image/png";
return "text/plain";
}
public static void main(String... args) throws Exception {
if (args.length != 1 || args[0].startsWith("-")) {
System.out.println("Usage: SpdyServer <base directory>");
return;
}
SpdyServer server = new SpdyServer(new File(args[0]),
SslContextBuilder.localhost().getSocketFactory());
server.run();
}
}