1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.squareup.okhttp.internal.framed; 18 19 import com.squareup.okhttp.Protocol; 20 import com.squareup.okhttp.internal.Platform; 21 import com.squareup.okhttp.internal.SslContextBuilder; 22 import com.squareup.okhttp.internal.Util; 23 import java.io.File; 24 import java.io.IOException; 25 import java.net.ProtocolException; 26 import java.net.ServerSocket; 27 import java.net.Socket; 28 import java.util.Arrays; 29 import java.util.List; 30 import java.util.logging.Level; 31 import java.util.logging.Logger; 32 import javax.net.ssl.SSLSocket; 33 import javax.net.ssl.SSLSocketFactory; 34 import okio.BufferedSink; 35 import okio.Okio; 36 import okio.Source; 37 38 /** A basic SPDY/HTTP_2 server that serves the contents of a local directory. */ 39 public final class FramedServer extends FramedConnection.Listener { 40 static final Logger logger = Logger.getLogger(FramedServer.class.getName()); 41 42 private final List<Protocol> framedProtocols = 43 Util.immutableList(Protocol.HTTP_2, Protocol.SPDY_3); 44 45 private final File baseDirectory; 46 private final SSLSocketFactory sslSocketFactory; 47 48 public FramedServer(File baseDirectory, SSLSocketFactory sslSocketFactory) { 49 this.baseDirectory = baseDirectory; 50 this.sslSocketFactory = sslSocketFactory; 51 } 52 53 private void run() throws Exception { 54 ServerSocket serverSocket = new ServerSocket(8888); 55 serverSocket.setReuseAddress(true); 56 57 while (true) { 58 Socket socket = null; 59 try { 60 socket = serverSocket.accept(); 61 62 SSLSocket sslSocket = doSsl(socket); 63 String protocolString = Platform.get().getSelectedProtocol(sslSocket); 64 Protocol protocol = protocolString != null ? Protocol.get(protocolString) : null; 65 if (protocol == null || !framedProtocols.contains(protocol)) { 66 throw new ProtocolException("Protocol " + protocol + " unsupported"); 67 } 68 FramedConnection framedConnection = new FramedConnection.Builder(false) 69 .socket(sslSocket) 70 .protocol(protocol) 71 .listener(this) 72 .build(); 73 framedConnection.sendConnectionPreface(); 74 } catch (IOException e) { 75 logger.log(Level.INFO, "FramedServer connection failure: " + e); 76 Util.closeQuietly(socket); 77 } catch (Exception e) { 78 logger.log(Level.WARNING, "FramedServer unexpected failure", e); 79 Util.closeQuietly(socket); 80 } 81 } 82 } 83 84 private SSLSocket doSsl(Socket socket) throws IOException { 85 SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket( 86 socket, socket.getInetAddress().getHostAddress(), socket.getPort(), true); 87 sslSocket.setUseClientMode(false); 88 Platform.get().configureTlsExtensions(sslSocket, null, framedProtocols); 89 sslSocket.startHandshake(); 90 return sslSocket; 91 } 92 93 @Override public void onStream(final FramedStream stream) throws IOException { 94 try { 95 List<Header> requestHeaders = stream.getRequestHeaders(); 96 String path = null; 97 for (int i = 0, size = requestHeaders.size(); i < size; i++) { 98 if (requestHeaders.get(i).name.equals(Header.TARGET_PATH)) { 99 path = requestHeaders.get(i).value.utf8(); 100 break; 101 } 102 } 103 104 if (path == null) { 105 // TODO: send bad request error 106 throw new AssertionError(); 107 } 108 109 File file = new File(baseDirectory + path); 110 111 if (file.isDirectory()) { 112 serveDirectory(stream, file.listFiles()); 113 } else if (file.exists()) { 114 serveFile(stream, file); 115 } else { 116 send404(stream, path); 117 } 118 } catch (IOException e) { 119 System.out.println(e.getMessage()); 120 } 121 } 122 123 private void send404(FramedStream stream, String path) throws IOException { 124 List<Header> responseHeaders = Arrays.asList( 125 new Header(":status", "404"), 126 new Header(":version", "HTTP/1.1"), 127 new Header("content-type", "text/plain") 128 ); 129 stream.reply(responseHeaders, true); 130 BufferedSink out = Okio.buffer(stream.getSink()); 131 out.writeUtf8("Not found: " + path); 132 out.close(); 133 } 134 135 private void serveDirectory(FramedStream stream, File[] files) throws IOException { 136 List<Header> responseHeaders = Arrays.asList( 137 new Header(":status", "200"), 138 new Header(":version", "HTTP/1.1"), 139 new Header("content-type", "text/html; charset=UTF-8") 140 ); 141 stream.reply(responseHeaders, true); 142 BufferedSink out = Okio.buffer(stream.getSink()); 143 for (File file : files) { 144 String target = file.isDirectory() ? (file.getName() + "/") : file.getName(); 145 out.writeUtf8("<a href='" + target + "'>" + target + "</a><br>"); 146 } 147 out.close(); 148 } 149 150 private void serveFile(FramedStream stream, File file) throws IOException { 151 List<Header> responseHeaders = Arrays.asList( 152 new Header(":status", "200"), 153 new Header(":version", "HTTP/1.1"), 154 new Header("content-type", contentType(file)) 155 ); 156 stream.reply(responseHeaders, true); 157 Source source = Okio.source(file); 158 try { 159 BufferedSink out = Okio.buffer(stream.getSink()); 160 out.writeAll(source); 161 out.close(); 162 } finally { 163 Util.closeQuietly(source); 164 } 165 } 166 167 private String contentType(File file) { 168 if (file.getName().endsWith(".css")) return "text/css"; 169 if (file.getName().endsWith(".gif")) return "image/gif"; 170 if (file.getName().endsWith(".html")) return "text/html"; 171 if (file.getName().endsWith(".jpeg")) return "image/jpeg"; 172 if (file.getName().endsWith(".jpg")) return "image/jpeg"; 173 if (file.getName().endsWith(".js")) return "application/javascript"; 174 if (file.getName().endsWith(".png")) return "image/png"; 175 return "text/plain"; 176 } 177 178 public static void main(String... args) throws Exception { 179 if (args.length != 1 || args[0].startsWith("-")) { 180 System.out.println("Usage: FramedServer <base directory>"); 181 return; 182 } 183 184 FramedServer server = new FramedServer(new File(args[0]), 185 SslContextBuilder.localhost().getSocketFactory()); 186 server.run(); 187 } 188 } 189