1c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath/* 2c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * Copyright (C) 2011 The Android Open Source Project 3c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * 4c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * Licensed under the Apache License, Version 2.0 (the "License"); 5c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * you may not use this file except in compliance with the License. 6c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * You may obtain a copy of the License at 7c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * 8c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * http://www.apache.org/licenses/LICENSE-2.0 9c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * 10c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * Unless required by applicable law or agreed to in writing, software 11c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * distributed under the License is distributed on an "AS IS" BASIS, 12c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * See the License for the specific language governing permissions and 14c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath * limitations under the License. 15c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath */ 16c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 172231db3e6bb54447a9b14cf004a6cb03c373651cjwilsonpackage com.squareup.okhttp.internal.spdy; 18c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 192231db3e6bb54447a9b14cf004a6cb03c373651cjwilsonimport com.squareup.okhttp.internal.SslContextBuilder; 20c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.io.File; 21c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.io.FileInputStream; 22c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.io.IOException; 23c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.io.InputStream; 24c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.io.OutputStream; 252231db3e6bb54447a9b14cf004a6cb03c373651cjwilsonimport java.io.OutputStreamWriter; 262231db3e6bb54447a9b14cf004a6cb03c373651cjwilsonimport java.io.Writer; 272231db3e6bb54447a9b14cf004a6cb03c373651cjwilsonimport java.net.InetAddress; 28c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.net.ServerSocket; 29c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.net.Socket; 30c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.util.Arrays; 31c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathimport java.util.List; 322231db3e6bb54447a9b14cf004a6cb03c373651cjwilsonimport javax.net.ssl.SSLContext; 332231db3e6bb54447a9b14cf004a6cb03c373651cjwilsonimport javax.net.ssl.SSLSocket; 342231db3e6bb54447a9b14cf004a6cb03c373651cjwilsonimport javax.net.ssl.SSLSocketFactory; 352231db3e6bb54447a9b14cf004a6cb03c373651cjwilsonimport org.eclipse.jetty.npn.NextProtoNego; 36c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 3754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson/** A basic SPDY server that serves the contents of a local directory. */ 38c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamathpublic final class SpdyServer implements IncomingStreamHandler { 3954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private final File baseDirectory; 4054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private SSLSocketFactory sslSocketFactory; 4154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 4254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson public SpdyServer(File baseDirectory) { 4354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson this.baseDirectory = baseDirectory; 4454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 4554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 4654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson public void useHttps(SSLSocketFactory sslSocketFactory) { 4754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson this.sslSocketFactory = sslSocketFactory; 4854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 4954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 5054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private void run() throws Exception { 5154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson ServerSocket serverSocket = new ServerSocket(8888); 5254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson serverSocket.setReuseAddress(true); 5354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 5454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson while (true) { 5554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson Socket socket = serverSocket.accept(); 5654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (sslSocketFactory != null) { 5754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson socket = doSsl(socket); 5854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 5954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson new SpdyConnection.Builder(false, socket).handler(this).build(); 60c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath } 6154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 6254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 6354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private Socket doSsl(Socket socket) throws IOException { 6454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson SSLSocket sslSocket = 6554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson (SSLSocket) sslSocketFactory.createSocket(socket, socket.getInetAddress().getHostAddress(), 6654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson socket.getPort(), true); 6754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson sslSocket.setUseClientMode(false); 6854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson NextProtoNego.put(sslSocket, new NextProtoNego.ServerProvider() { 6954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson @Override public void unsupported() { 7054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson System.out.println("UNSUPPORTED"); 7154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 7254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson @Override public List<String> protocols() { 7354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson return Arrays.asList("spdy/3"); 7454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 7554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson @Override public void protocolSelected(String protocol) { 7654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson System.out.println("PROTOCOL SELECTED: " + protocol); 7754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 7854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson }); 7954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson return sslSocket; 8054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 8154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 8254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson @Override public void receive(final SpdyStream stream) throws IOException { 8354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson List<String> requestHeaders = stream.getRequestHeaders(); 8454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson String path = null; 8554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson for (int i = 0; i < requestHeaders.size(); i += 2) { 8654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson String s = requestHeaders.get(i); 8754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (":path".equals(s)) { 8854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson path = requestHeaders.get(i + 1); 8954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson break; 9054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 912231db3e6bb54447a9b14cf004a6cb03c373651cjwilson } 922231db3e6bb54447a9b14cf004a6cb03c373651cjwilson 9354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (path == null) { 9454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson // TODO: send bad request error 9554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson throw new AssertionError(); 962231db3e6bb54447a9b14cf004a6cb03c373651cjwilson } 977899c5ab935cf542069835ec7d3e457db596dbf7Narayan Kamath 9854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson File file = new File(baseDirectory + path); 99c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 10054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (file.isDirectory()) { 10154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson serveDirectory(stream, file.list()); 10254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } else if (file.exists()) { 10354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson serveFile(stream, file); 10454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } else { 10554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson send404(stream, path); 106c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath } 10754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 10854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 10954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private void send404(SpdyStream stream, String path) throws IOException { 11054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson List<String> responseHeaders = 11154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson Arrays.asList(":status", "404", ":version", "HTTP/1.1", "content-type", "text/plain"); 11254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson stream.reply(responseHeaders, true); 11354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson OutputStream out = stream.getOutputStream(); 11454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson String text = "Not found: " + path; 11554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson out.write(text.getBytes("UTF-8")); 11654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson out.close(); 11754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 11854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 11954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private void serveDirectory(SpdyStream stream, String[] files) throws IOException { 12054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson List<String> responseHeaders = 12154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson Arrays.asList(":status", "200", ":version", "HTTP/1.1", "content-type", 12254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson "text/html; charset=UTF-8"); 12354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson stream.reply(responseHeaders, true); 12454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson OutputStream out = stream.getOutputStream(); 12554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson Writer writer = new OutputStreamWriter(out, "UTF-8"); 12654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson for (String file : files) { 12754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson writer.write("<a href='" + file + "'>" + file + "</a><br>"); 128c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath } 12954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson writer.close(); 13054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 13154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson 13254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private void serveFile(SpdyStream stream, File file) throws IOException { 13354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson InputStream in = new FileInputStream(file); 13454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson byte[] buffer = new byte[8192]; 13554cf3446000fdcf88a9e62724f7deb0282e98da1jwilson stream.reply( 13654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson Arrays.asList(":status", "200", ":version", "HTTP/1.1", "content-type", contentType(file)), 13754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson true); 13854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson OutputStream out = stream.getOutputStream(); 13954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson int count; 14054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson while ((count = in.read(buffer)) != -1) { 14154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson out.write(buffer, 0, count); 1422231db3e6bb54447a9b14cf004a6cb03c373651cjwilson } 14354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson out.close(); 14454cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 1452231db3e6bb54447a9b14cf004a6cb03c373651cjwilson 14654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson private String contentType(File file) { 14754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson return file.getName().endsWith(".html") ? "text/html" : "text/plain"; 14854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 149c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 15054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson public static void main(String... args) throws Exception { 15154cf3446000fdcf88a9e62724f7deb0282e98da1jwilson if (args.length != 1 || args[0].startsWith("-")) { 15254cf3446000fdcf88a9e62724f7deb0282e98da1jwilson System.out.println("Usage: SpdyServer <base directory>"); 15354cf3446000fdcf88a9e62724f7deb0282e98da1jwilson return; 154c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath } 155c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath 15654cf3446000fdcf88a9e62724f7deb0282e98da1jwilson SpdyServer server = new SpdyServer(new File(args[0])); 15754cf3446000fdcf88a9e62724f7deb0282e98da1jwilson SSLContext sslContext = new SslContextBuilder(InetAddress.getLocalHost().getHostName()).build(); 15854cf3446000fdcf88a9e62724f7deb0282e98da1jwilson server.useHttps(sslContext.getSocketFactory()); 15954cf3446000fdcf88a9e62724f7deb0282e98da1jwilson server.run(); 16054cf3446000fdcf88a9e62724f7deb0282e98da1jwilson } 161c3f6f16bd4a2338e88275641b9f2f56e816ca377Narayan Kamath} 162