1 /* 2 * Copyright (C) 2014 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.example.android.permissionrequest; 18 19 import android.content.res.AssetManager; 20 import android.text.TextUtils; 21 import android.util.Log; 22 23 import java.io.BufferedReader; 24 import java.io.ByteArrayOutputStream; 25 import java.io.FileNotFoundException; 26 import java.io.IOException; 27 import java.io.InputStream; 28 import java.io.InputStreamReader; 29 import java.io.PrintStream; 30 import java.net.ServerSocket; 31 import java.net.Socket; 32 import java.net.SocketException; 33 34 /** 35 * Implementation of a very basic HTTP server. The contents are loaded from the assets folder. This 36 * server handles one request at a time. It only supports GET method. 37 */ 38 public class SimpleWebServer implements Runnable { 39 40 private static final String TAG = "SimpleWebServer"; 41 42 /** 43 * The port number we listen to 44 */ 45 private final int mPort; 46 47 /** 48 * {@link android.content.res.AssetManager} for loading files to serve. 49 */ 50 private final AssetManager mAssets; 51 52 /** 53 * True if the server is running. 54 */ 55 private boolean mIsRunning; 56 57 /** 58 * The {@link java.net.ServerSocket} that we listen to. 59 */ 60 private ServerSocket mServerSocket; 61 62 /** 63 * WebServer constructor. 64 */ 65 public SimpleWebServer(int port, AssetManager assets) { 66 mPort = port; 67 mAssets = assets; 68 } 69 70 /** 71 * This method starts the web server listening to the specified port. 72 */ 73 public void start() { 74 mIsRunning = true; 75 new Thread(this).start(); 76 } 77 78 /** 79 * This method stops the web server 80 */ 81 public void stop() { 82 try { 83 mIsRunning = false; 84 if (null != mServerSocket) { 85 mServerSocket.close(); 86 mServerSocket = null; 87 } 88 } catch (IOException e) { 89 Log.e(TAG, "Error closing the server socket.", e); 90 } 91 } 92 93 @Override 94 public void run() { 95 try { 96 mServerSocket = new ServerSocket(mPort); 97 while (mIsRunning) { 98 Socket socket = mServerSocket.accept(); 99 handle(socket); 100 socket.close(); 101 } 102 } catch (SocketException e) { 103 // The server was stopped; ignore. 104 } catch (IOException e) { 105 Log.e(TAG, "Web server error.", e); 106 } 107 } 108 109 /** 110 * Respond to a request from a client. 111 * 112 * @param socket The client socket. 113 * @throws IOException 114 */ 115 private void handle(Socket socket) throws IOException { 116 BufferedReader reader = null; 117 PrintStream output = null; 118 try { 119 String route = null; 120 121 // Read HTTP headers and parse out the route. 122 reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); 123 String line; 124 while (!TextUtils.isEmpty(line = reader.readLine())) { 125 if (line.startsWith("GET /")) { 126 int start = line.indexOf('/') + 1; 127 int end = line.indexOf(' ', start); 128 route = line.substring(start, end); 129 break; 130 } 131 } 132 133 // Output stream that we send the response to 134 output = new PrintStream(socket.getOutputStream()); 135 136 // Prepare the content to send. 137 if (null == route) { 138 writeServerError(output); 139 return; 140 } 141 byte[] bytes = loadContent(route); 142 if (null == bytes) { 143 writeServerError(output); 144 return; 145 } 146 147 // Send out the content. 148 output.println("HTTP/1.0 200 OK"); 149 output.println("Content-Type: " + detectMimeType(route)); 150 output.println("Content-Length: " + bytes.length); 151 output.println(); 152 output.write(bytes); 153 output.flush(); 154 } finally { 155 if (null != output) { 156 output.close(); 157 } 158 if (null != reader) { 159 reader.close(); 160 } 161 } 162 } 163 164 /** 165 * Writes a server error response (HTTP/1.0 500) to the given output stream. 166 * 167 * @param output The output stream. 168 */ 169 private void writeServerError(PrintStream output) { 170 output.println("HTTP/1.0 500 Internal Server Error"); 171 output.flush(); 172 } 173 174 /** 175 * Loads all the content of {@code fileName}. 176 * 177 * @param fileName The name of the file. 178 * @return The content of the file. 179 * @throws IOException 180 */ 181 private byte[] loadContent(String fileName) throws IOException { 182 InputStream input = null; 183 try { 184 ByteArrayOutputStream output = new ByteArrayOutputStream(); 185 input = mAssets.open(fileName); 186 byte[] buffer = new byte[1024]; 187 int size; 188 while (-1 != (size = input.read(buffer))) { 189 output.write(buffer, 0, size); 190 } 191 output.flush(); 192 return output.toByteArray(); 193 } catch (FileNotFoundException e) { 194 return null; 195 } finally { 196 if (null != input) { 197 input.close(); 198 } 199 } 200 } 201 202 /** 203 * Detects the MIME type from the {@code fileName}. 204 * 205 * @param fileName The name of the file. 206 * @return A MIME type. 207 */ 208 private String detectMimeType(String fileName) { 209 if (TextUtils.isEmpty(fileName)) { 210 return null; 211 } else if (fileName.endsWith(".html")) { 212 return "text/html"; 213 } else if (fileName.endsWith(".js")) { 214 return "application/javascript"; 215 } else if (fileName.endsWith(".css")) { 216 return "text/css"; 217 } else { 218 return "application/octet-stream"; 219 } 220 } 221 222 } 223