整体结构就是使用ServerSocket监听一个地址,当有接受到请求之后,解析请求中的资源路径。服务器资源路径存放在项目下的一个目录中,服务器会到这个目录中根据请求的路径去寻找相应的资源。如果找到了则返回该文件内容,否则提示找不到文件。

功能主要分为三块,一块是监听IP和端口号;一块是接受HTTP请求报文,并解析报文;最后是处理和返回响应。

HttpServer.java

package com.oolong.webserver;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket; public class HttpServer { public static final String WEB_ROOT =
System.getProperty("user.dir") + File.separator + "webroot";
private final int port = 8080;
private boolean isShutdown = false; // 表示服务器是否关闭 public void waiting() {
ServerSocket serverSocket = null; try {
serverSocket = new ServerSocket(port, 1,
InetAddress.getByName("127.0.0.1"));
} catch (IOException ex) {
ex.printStackTrace();
System.exit(1);
} // 循环等待请求
while(!isShutdown) {
Socket socket = null;
InputStream input = null;
OutputStream output = null; try {
// 从服务器等待队列中获取一个连接
socket = serverSocket.accept();
input = socket.getInputStream();
output = socket.getOutputStream(); // 从输入中解析请求字符串,生成一个请求对象
HttpRequest request = new HttpRequest(input);
request.parse(); // 创建一个响应对象返回内容
HttpResponse response = new HttpResponse(output);
response.setRequest(request);
response.sendStaticResource(); // 关闭socket
socket.close(); } catch (Exception ex) {
ex.printStackTrace();
}
}
} public static void main(String[] args) {
HttpServer server = new HttpServer();
server.waiting();
}
}

这里利用ServerSocket接受请求的Socket:

socket = serverSocket.accept();

然后从请求的Socket中获取到输入流和输出流:

input = socket.getInputStream();
output = socket.getOutputStream();

将输入流交给一个HttpRequest对象处理,进行解析请求:

HttpRequest request = new HttpRequest(input);
request.parse();

将解析后的请求对象和输出流交给HttpResponse对象,用来返回响应:

HttpResponse response = new HttpResponse(output);
response.setRequest(request);
response.sendStaticResource();

下面分别看看请求对象和响应对象的实现。

HttpRequest.java

package com.oolong.webserver;

import java.io.IOException;
import java.io.InputStream; public class HttpRequest { private InputStream input;
private String uri; public HttpRequest(InputStream input) {
this.input = input;
} public void parse() {
// 从socket中读取字符流
StringBuffer requestStr = new StringBuffer(2048);
int i;
byte[] buffer = new byte[2048]; try {
i = input.read(buffer);
} catch (IOException ex) {
ex.printStackTrace();
i = -1;
} for (int j = 0; j < i; j++) {
requestStr.append((char) buffer[j]);
} System.out.println(requestStr.toString());
uri = parseUri(requestStr.toString());
System.out.println(uri);
} private String parseUri(String requestStr) {
int index1, index2;
index1 = requestStr.indexOf(' '); if (index1 != -1) {
index2 = requestStr.indexOf(' ', index1 + 1); if (index2 > index1) {
return requestStr.substring(index1 + 1, index2);
}
} return null;
} public String getUri() {
return uri;
}
}

可以看到这个请求对象中主要就是parse()这个解析请求字符的方法,以及parseUri()这个解析URI的方法。

首先看parse(),它创建了一个缓冲区,然后从输入流中读取请求字符串。然后调用parseUri()解析请求路径。

HttpResponse.java

这个是处理响应的类,看起来稍显复杂。其实只是根据请求解析的URI到资源目录中去寻找对应的文件,然后将文件写入Socket的输出流中。

如果没有找到,则输出一段错误信息即可。

package com.oolong.webserver;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStream; public class HttpResponse { private static final int BUFFER_SIZE = 1024;
private HttpRequest request;
private OutputStream output; public HttpResponse(OutputStream output) {
this.output = output;
} public void setRequest(HttpRequest request) {
this.request = request;
} public void sendStaticResource() throws IOException {
byte[] bytes = new byte[BUFFER_SIZE];
FileInputStream fis = null;
String filePath = request.getUri() == null ? "" : request.getUri().trim(); // 处理根路径
if (filePath.equals("/")) {
filePath = "/index.html";
} try {
String page = null;
File file = new File(HttpServer.WEB_ROOT, filePath);
Long fileLength = file.length();
byte[] fileContent = new byte[fileLength.intValue()]; if (file.exists()) {
fis = new FileInputStream(file);
fis.read(fileContent);
fis.close(); page = new String(fileContent);
page = warpMessage("200", page);
output.write(page.getBytes()); } else {
String errorMessage = warpMessage("404", "404 File Not Found!");
output.write(errorMessage.getBytes());
}
} catch(Exception ex) {
ex.printStackTrace();
}
} private String warpMessage(String statusCode, String message) {
return "HTTP/1.1 " + statusCode + "\r\n" +
"Content-Type: text/html\r\n" +
"Content-Length: " + message.length() + "\r\n" +
"\r\n" + message;
} public static void main(String[] args) {
System.out.println(HttpServer.WEB_ROOT);
}
}

测试

在项目的根目录下的webroot目录中创建一个简单的index.html页面。

<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
Hello World!
</body>
</html>

运行HttpServer。然后在浏览器中输入http://127.0.0.1:8080/ 或者http://127.0.0.1:8080/index.html

可以看到:

如果输入其他地址,如:http://127.0.0.1:8080/home.html

注意

你可能在调试的时候会发现,在浏览器发起一次请求的时候,

socket = serverSocket.accept();

可能会执行两次,这是因为浏览器会自动发起一次对icon的请求。这个是浏览器的特性,与代码无关,不是bug。

Socket——实现一个简单的静态网页服务器的更多相关文章

  1. socket编程——一个简单的样例

    从一个简单的使用TCP样例開始socket编程,其基本过程例如以下: server                                                  client ++ ...

  2. 使用Socket模拟一个简单的Webservice调用

    webservice是对socket的一个封装,让远程调用调用变得更加简单,那么使用socket究竟有多么麻烦呢?来看看. 做一个简单的天气查询: 服务端: public class SocketSe ...

  3. socket计划——一个简单的例子

    从一个简单易用TCP样品开始socket计划,的基本过程例如下列: server                                                  client +++ ...

  4. socket编程——一个简单的例子

    从一个简单的使用TCP例子开始socket编程,其基本步骤如下: server                                                  client ++++ ...

  5. 初始nginx(启动运行) 使用nginx做一个简单的静态资源服务器

    第一次接触nginx的时候,那时候公司还是用的一些不知名的小技术,后来公司发展问题,重新招了人,然后接触到nginx,公司 使用nginx用来做代理服务器,所有请求 都先经过nginx服务器,然后交由 ...

  6. Socket实现一个简单的半双工通信

    Socket是client进行在网络与server进行数据交互的一种基本通信方式.通信有三种通信.即单工.半双工,和全双工. 所谓单工,就是仅仅可以进行单向通信,如bb机. 而半双工就是一来一回的通信 ...

  7. Css打造一个简单的静态七巧板

    偶然在微博上看到用css写一个七巧板,正好也有一些源代码,于是就试着敲了敲. 主要是利用了css3的transform,实现平移,旋转,变形,直接用看到的代码敲出来之后有些问题,因为宽度上下面绿色的三 ...

  8. socket实现一个简单的echo服务

    服务端的实现: public class EchoServer{ //创建一个serverSocket private final ServerSocket serverSocket; //创建一个构 ...

  9. 用socket写一个简单的服务器

    import socketsk=socket.socket()sk.bind(("127.0.0.1",7001))sk.listen()def login(url): with ...

随机推荐

  1. js多语言切换demo

    网站为了国际化的需要,会使用到语言包,案例如下图. 这次尝试用js来打语言包,用到了插件 jquery.i18n.properties ,很明显,使用这个插件需要先加载jquery. 代码布局结构   ...

  2. 常见DML语句汇总

    DML操作是指对数据中表记录的操作,主要包括表记录的插入(insert).更新(update).删除(delete)和查询(select),是开发人员日常使用最频繁的操作,下面依次对它们进行介绍. ( ...

  3. Oracle【序列、索引、视图、分页】

    1.Oracle序列语法:create sequence 序列名 特点1:默认是无值,指针指向没有值的位置 特点2:序列名.nextval 每次执行值会自增一次,步长为 1 特点3:序列名.currv ...

  4. 4.NIO_Channel 通道

    1.通道(Channel) 由 java.nio.channels 包定义的.Channel 表示 IO 源与目标打开的连接.Channel 类似于传统的“流”.只不过 Channel 本身不能直接访 ...

  5. Linux useradd userdel groupadd groupdel gpasswd(组成员管理) id groups

    添加用户 useradd [选项] 用户名 -u :指定UID标记号 -d:指定宿主目录,缺省为/home/用户名 -g:指定所属的基本组(组名或GID) -G: 指定所属的附加组(组名或GID) - ...

  6. Appium安装(环境配置)

    Appium官方网站:http://appium.io/ 首页底部给出安装步骤, 一.Appium官网提示首先安装node.js,上https://nodejs.org/找找看,下载完后仅用你0.01 ...

  7. QTP(7)

    一.输出值(Output Value) 1.应用场景: 1) 关心被测系统的数据 2) 将被测系统生成的数据作为后面步骤的输入 2.输出值就是输出被测系统中实际运行时的数据的一种技术 a.运行中对象的 ...

  8. 《Python基础教程》第一章:基础知识

    如果希望只执行普通的除法,可以在程序前加上以下语句:from __future__ import division.还有另外一个方法,如果通过命令行运行Python, 可以使用命令开关-Qnew.此时 ...

  9. 帝都之行9day:正式上班第一天

    今天是我正式上班的第一天. 面了两天,三家公司,然后周五就去办入职了,我是不是太随便了点,捂脸. 不管怎么说,又要开始上班啦,CRUD的日子又要开始了…… 加油吧!

  10. 图片框住一个小视频 谈css padding百分比自适应

    今天市场提出活动页,活动页有一块内容是在一个手机背景图框里播放视频,网页是适配的,设计师只给我一张带有手机壳的背景图. 如果用JS画应该也是可以的,但一个简单的活动页没必要,快速实现用背景图调CSS最 ...