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