2014-02-06 1 views
0

Я попытался реализовать простой HTTP-сервер с сокетом на Java. То, что он делает, - это принять имя файла из клиентского браузера, открыть этот файл на диске и распечатать его в браузере. Мой текущий код показан ниже:Реализация HTTP-сервера с помощью Socket - как сохранить его навсегда?

public class HTTPServer { 

    public String getFirstLine(Scanner s) { 
     String line = ""; 
     if (s.hasNextLine()) { 
      line = s.nextLine(); 
     } 
     if (line.startsWith("GET /")) { 
      return line; 
     } 
     return null; 
    } 

    public String getFilePath(String s) { 
     int beginIndex = s.indexOf("/"); 
     int endIndex = s.indexOf(" ", beginIndex); 
     return s.substring(beginIndex + 1, endIndex); 
    } 

    /** 
    * @param args the command line arguments 
    */ 
    public static void main(String[] args) throws IOException { 
     Socket clientSocket = null; 
     int serverPort = 7777; // the server port 

     try { 
      ServerSocket listenSocket = new ServerSocket(serverPort); 

      while (true) { 
       clientSocket = listenSocket.accept(); 
       Scanner in; 
       PrintWriter out; 
       HTTPServer hs; 
       in = new Scanner(clientSocket.getInputStream()); 
       out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()))); 
       hs = new HTTPServer(); 
       // get the first line 
       String httpGetLine = hs.getFirstLine(in); 
       if (httpGetLine != null) { 
        // parse the file path 
        String filePath = hs.getFilePath(httpGetLine); 
        // open the file and read it 
        File f = new File(filePath); 

        if (f.exists()) { 
         // if the file exists, response to the client with its content 
         out.println("HTTP/1.1 200 OK\n\n"); 
         out.flush(); 
         BufferedReader br = new BufferedReader(new FileReader(filePath)); 
         String fileLine; 
         while ((fileLine = br.readLine()) != null) { 
          out.println(fileLine); 
          out.flush(); 
         } 
        } else { 
         out.println("HTTP/1.1 404 NotFound\n\nFile " + filePath + " not found."); 
         out.flush(); 
        } 
       } 
      } 

     } catch (IOException e) { 
      System.out.println("IO Exception:" + e.getMessage()); 
     } finally { 
      try { 
       if (clientSocket != null) { 
        clientSocket.close(); 
       } 
      } catch (IOException e) { 
       // ignore exception on close 
      } 
     } 
    } 

} 

После того как я запустить его в NetBeans, открыть браузер и посетить «локальный: 7777/hello.html» (hello.html файл в папке проекта). Он просто показывает, что страница загружается. Только после того, как я остановлю свой сервер в NetBeans, содержимое hello.html будет показано в браузере.

Я хочу, чтобы мой сервер работал бесконечно, отвечал на запросы GET один за другим и отображал клиенту содержимое файла. Я не уверен, какие части моего кода должны быть помещены в цикл while(true), а какие нет.

ответ

1

Вам нужно закрыть розетку, когда вы закончите с ней.

2

Ваша логика кода очень неполна даже для минималистичного HTTP-сервера. Вы не следуете основным правилам, определяемым RFC 2616.

Вы вообще не читаете заголовки HTTP-запросов клиента. Вы читаете только первую строку. Заголовки определяют, как должен себя вести сервер, какой тип ответа он должен отправить, как отправить ответ и т. Д.

Вы не проверяете версию HTTP-запроса клиента. Не отправляйте ответ HTTP 1.1 на запрос HTTP 1.0, но вы можете отправить ответ HTTP 1.0 на запрос HTTP 1.1.

Вы не проверяете, имеет ли HTTP-запрос клиента заголовок Connection. Соединения HTTP 1.0 по умолчанию не используют keep-alives, поэтому клиент HTTP 1.0 должен явно запрашивать keep-alive, отправив заголовок Connection: keep-alive. Соединения HTTP 1.1 используют keep-alives по умолчанию, поэтому клиент HTTP 1.1 может явно отключить keep-alive, отправив вместо этого заголовок Connection: close. Если запрос HTTP 1.0 не содержит заголовок Connection: keep-alive, тогда сервер ДОЛЖЕН принять close. Если запрос HTTP 1.1 не содержит заголовок Connection: close, тогда сервер ДОЛЖЕН принять keep-alive. В любом случае, вы должны отправить свой собственный заголовок Connection, указав, будет ли keep-alive или close используется вашим сервером (если запрашивается keep-alive, вам не нужно его соблюдать, но вам следует по возможности). В случае close, вы ДОЛЖНЫ закрыть розетку после отправки ответа.

В вашем 200 ответа, вы не посылаешь Content-Length заголовка (хотя Transfer-Encoding: chunked подход был бы более подходящим для типа отправки вы делаете, но только если клиент послал HTTP 1.1 запроса см. RFC 2616 Section 3.6.1 для получения более подробной информации), поэтому вы ДОЛЖНЫ закрыть сокет (и отправить заголовок Connection: close) после отправки ответа, иначе клиент не сможет узнать, когда достигнут EOF. См. RFC 2616 Section 4.4 для более подробной информации.

Ваш код обслуживает только одного клиента и один запрос за раз. Если вы хотите, чтобы ваш сервер обрабатывал несколько клиентов одновременно, особенно если вы хотите поддерживать HTTP-хранители (что вам нужно), тогда вам нужно создать рабочий поток для каждого подключаемого клиента, и этот поток может обслуживать множество когда клиент отправляет, пока он не отключится.

Попробуйте что-то больше, как это (возможно, потребуется перенастройка для компиляции, отслеживания потоков, и т.д.):

public class HTTPClientThread extends Thread { 

    private Socket clientSocket; 

    public HTTPClientThread(Socket client) { 
     clientSocket = client; 
    } 

    public void run() { 

     Scanner in = new Scanner(clientSocket.getInputStream()); 
     OutputStream out = clientSocket.getOutputStream(); 
     PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(out))); 
     bool keepAlive = false; 

     do { 
      String requestLine = s.nextLine(); 
      String line; 
      String connection = ""; 
      keepAlive = false; 

      do { 
       line = s.nextLine(); 
       if (line == "") { 
        break; 
       } 
       if (line.startsWith("Connection:") { 
        connection = line.substring(11).trim(); 
       } 
      } 
      while (true); 

      int idx = requestLine.indexOf(" "); 
      if (idx == -1) { 
       pw.println("HTTP/1.0 400 Bad Request"); 
       pw.println("Connection: close"); 
       pw.println(""); 
       pw.flush(); 
       continue; 
      } 

      String httpMethod = line.substring(0, idx); 
      line = line.substring(idx+1); 

      idx = line.indexOf(" "); 
      if (idx == -1) { 
       pw.println("HTTP/1.0 400 Bad Request"); 
       pw.println("Connection: close"); 
       pw.println(""); 
       pw.flush(); 
       continue; 
      } 

      String httpVersion = line.subString(endIndex+1); 
      if (!httpVersion.equals("HTTP/1.0") && !httpVersion.equals("HTTP/1.1")) { 
       pw.println("HTTP/1.0 505 HTTP Version Not Supported"); 
       pw.println("Connection: close"); 
       pw.println(""); 
       pw.flush(); 
       continue; 
      } 

      if (connection != "") { 
       keepAlive = connection.equalsIgnoreCase("keep-alive"); 
      } 
      else if (httpVersion.equals("HTTP/1.1")) { 
       keepAlive = true; 
      } 
      else { 
       keepAlive = false; 
      } 

      String filePath = line.substring(0, endIndex); 
      if (filePath.startsWith("/")) { 
       filePath = filePath.substring(1); 
      } 

      // open the file and read it 
      File f = new File(filePath); 

      if (!f.exists()) { 
       pw.println(httpVersion + " 404 Not Found"); 
       pw.println("Content-Length: 0"); 
       if (keepAlive) { 
        pw.println("Connection: keep-alive"); 
       } else { 
        pw.println("Connection: close"); 
       } 
       pw.println(""); 
       pw.flush(); 
       continue; 
      } 

      if (httpMethod != "GET") { 
       pw.println(httpVersion +" 405 Method Not Allowed"); 
       pw.println("Allow: GET"); 
       if (keepAlive) { 
        pw.println("Connection: keep-alive"); 
       } else { 
        pw.println("Connection: close"); 
       } 
       pw.println(""); 
       pw.flush(); 
       continue; 
      } 

      pw.println(httpVersion + " 200 OK"); 
      pw.println("Content-Type: application/octet-stream"); 
      pw.print("Content-Length: "); 
      pw.println(f.length()); 
      if (keepAlive) { 
       pw.println("Connection: keep-alive"); 
      } else { 
       pw.println("Connection: close"); 
      } 
      pw.println(""); 
      pw.flush(); 

      FileInputStream fis = new FileInputStream(f); 
      BufferedOutputStream bw = new BufferedOutputStream(out); 
      byte[] buffer = new byte[1024]; 
      int buflen; 

      while ((buflen = fis.read(buffer)) > 0) { 
       bw.write(buffer, 0, buflen); 
       bw.flush(); 
      } 
     } 
     while (keepAlive); 

     clientSocket.close(); 
    } 
} 

public class HTTPServer { 

    /** 
    * @param args the command line arguments 
    */ 
    public static void main(String[] args) throws IOException { 
     Socket clientSocket = null; 
     int serverPort = 7777; // the server port 

     try { 
      ServerSocket listenSocket = new ServerSocket(serverPort); 

      while (true) { 
       clientSocket = listenSocket.accept(); 
       new HTTPClientThread(clientSocket); 
      } 

     } catch (IOException e) { 
      System.out.println("IO Exception:" + e.getMessage()); 
     } 
    } 
} 

С учетом сказанного, Java имеет свой собственный HTTP server class доступны.

1

while (true) оператор будет выполнять эти две строки на неопределенный срок. Разве это не займет весь процессорный ресурс? Не должно ли быть какое-то событие или поток сна?

+1

Лучше переформулируйте свой ответ. Ответы, которые в основном содержат вопросительные знаки, можно легко воспринимать как «не ответ» ;-) – GhostCat

 Смежные вопросы

  • Нет связанных вопросов^_^