本章主要讲述一个java的web服务是如何工作的。
web也叫HTTP服务,因为他使用HTTP协议与客户端进行交互(通常使用的是浏览器),一个基于java的web服务主要涉及到2个重要的类:java.net.Socket
和java.net.ServerSocket
并通过发送HTTP消息进行通信。
超文本传输协议(HTTP)
HTTP是一种协议,浏览器和web服务之间可以通过他在互联网上接收和发送数据。客户端请求一个文件而服务器端进行响应。HTTP使用的是TCP连接,默认端口80。现在HTTP协议基本上都是1.1版本。
HTTP请求
一个HTTP Request由以下三个部分组成:
- Method—Uniform Resource Identifier (URI)—Protocol/Version
- Request headers
- Entity body
看一个实际的HTTP Request的例子:
//请求方法-URI-协议/版本号
POST /examples/default.jsp HTTP/1.1
//请求头
Accept: text/plain; text/html
Accept-Language: en-gb
Connection: Keep-Alive
Host: localhost
User-Agent: Mozilla/4.0 (compatible; MSIE 4.01; Windows 98)
Content-Length: 33
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
//请求体
lastName=Franks&firstName=Michael
HTTP请求的请求方法有很多,GET,POST,HEAD,OPTIONS,PUT,DELETE和TRACE,GET和POST是应用中用的最多的请求方法。
URI-统一资源标识符,用于表示某一个互联网资源。他可以理解为是相对于服务器的根目录,所以以”/”开头
请求头包含客户端环境和请求体的有用信息,比如浏览器信息,请求体的长度设置等
HTTP响应
HTTP Response也包含有三个部分
- Protocol—Status code—Description
- Response headers
- Entity body
看好一个响应的例子:
// 协议/版本-状态码-描述
HTTP/1.1 200 OK
// 响应头
Server: Microsoft-IIS/4.0
Date: Mon, 5 Jan 2004 13:13:33 GMT
Content-Type: text/html
Last-Modified: Mon, 5 Jan 2004 13:13:12 GMT
Content-Length: 112
// 响应体
<html>
<head>
<title>HTTP Response Example</title>
</head>
<body>
Welcome to Brainy Software
</body>
</html>
Socket Class
Socket 是网络连接中的一个端点,通过socket可以使应用读写网络资源。通过此连接,两个不同的软件应用可以通过字节流进行通信。当然,前提是你需要知道对应应用的ip和端口号。在java中,socket是通过java.net.socket类呈现的。
Socket 类的构造方法有很多,例如:
//通过ip和端口
Socket(InetAddress address, int port)
//通过主机名和端口
Socket(String host, int port)
//创建实例
new Socket ("yahoo.com", 80);
其他的就不一一列举了
下面来看一段代码:
public class SocketClass {
public static void main(String[] args) {
try {
//建立连接
Socket client = new Socket("127.0.0.1",8080);
//创建流
OutputStream os = client.getOutputStream();
boolean autoFlush = true;
PrintWriter ps = new PrintWriter(client.getOutputStream(), autoFlush);
BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream()));
//向web服务器发送HTTP请求
ps.println("GET /index.jsp HTTP/1.1");
ps.println("Host: localhost:8080");
ps.println("Connection: Close");
ps.println();
boolean loop = true;
StringBuffer sb = new StringBuffer(8096);
while (loop) {
if ( br.ready() ) {
int i=0;while (i!=-1) {
i = br.read();
sb.append((char) i);
}
loop = false;
}
Thread.currentThread().sleep(50);
}
// display the response to the out console
System.out.println(sb.toString());
client.close();
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
这是客户端代码,向服务器发送请求。找根目录下的index.jsp页面。并打印相应信息。
ServerSocket class
上述的Socket class代表的实际上是一个客户端的socket,无论何时,如果你想和服务器取得连接,只需要通过构造方法创建对象即可。
而ServerSocket与Socket不同之处是它必须任何时候都处于等待连接的状态。需要用到的类:java.net.ServerSocket
ServerSocket
和Socket
不同,它的定位就是等待客户端连接,服务器端socket到一个连接请求,它就会创建一个Socket
实例来和客户端的Socket
进行通信
使用构造创建ServerSocket:
常用的构造有
public ServerSocket(int port, int backLog, InetAddress bindingAddress);
这里被绑定的地址必须是 InetAddress 的对象,我们一般可以调用InetAddress.getByName("127.0.0.1"); 来获取他的对象。
如:
new ServerSocket(8080, 1, InetAddress.getByName("127.0.0.1"));
应用代码
我们编写三个类来作为一个简单的服务器应用
- HttpServer
- Request
- Response
The HttpServer Class:
public class HttpServer {
/** WEB_ROOT 是放html和file文件的地方
* 这个应用中,WEB_ROOT 就是项目目录下的webroot文件夹
*/
//获取项目路径下的webroot目录
public static final String WEB_ROOT =
System.getProperty("user.dir") + File.separator + "webroot";
// 关闭命令
private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";
// 接收关闭命令
private boolean shutdown = false;
public static void main(String[] args) {
HttpServer server = new HttpServer();
server.await();
}
public void await() {
ServerSocket serverSocket = null;
int port = 8080;
try {
serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));
}
catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
// Loop waiting for a request
while (!shutdown) {
Socket socket = null;
InputStream input = null;
OutputStream output = null;
try {
//等待接收
socket = serverSocket.accept();
input = socket.getInputStream();
output = socket.getOutputStream();
// 创建Request对象(Request实现代码在下面)
Request request = new Request(input);
request.parse();
// 创建Response对象
Response response = new Response(output);
response.setRequest(request);
response.sendStaticResource();
// 关闭socket
socket.close();
//检测先前的URI是否是关闭命令
shutdown = request.getUri().equals(SHUTDOWN_COMMAND);
}
catch (Exception e) {
e.printStackTrace();
continue;
}
}
}
}
The Request class:
public class Request {
private InputStream input;
private String uri;
public Request(InputStream input) {
this.input = input;
}
public void parse() {
// Read a set of characters from the socket
StringBuffer request = new StringBuffer(2048);
int i;
byte[] buffer = new byte[2048];
try {
i = input.read(buffer);
}
catch (IOException e) {
e.printStackTrace();
i = -1;
}
for (int j=0; j<i; j++) {
request.append((char) buffer[j]);
}
System.out.print(request.toString());
uri = parseUri(request.toString());
}
//截取 "请求"内容的两个"空格"之间的uri
private String parseUri(String requestString) {
int index1, index2;
index1 = requestString.indexOf(' ');
if (index1 != -1) {
index2 = requestString.indexOf(' ', index1 + 1);
if (index2 > index1)
return requestString.substring(index1 + 1, index2);
}
return null;
}
public String getUri() {
return uri;
}
}
The Response class:
public class Response {
private static final int BUFFER_SIZE = 1024;
Request request;
OutputStream output;
public Response(OutputStream output) {
this.output = output;
}
public void setRequest(Request request) {
this.request = request;
}
//读取request对象的URI中的内容
public void sendStaticResource() throws IOException {
byte[] bytes = new byte[BUFFER_SIZE];
FileInputStream fis = null;
try {
File file = new File(HttpServer.WEB_ROOT, request.getUri());
if (file.exists()) {
fis = new FileInputStream(file);
int ch = fis.read(bytes, 0, BUFFER_SIZE);
while (ch!=-1) {
output.write(bytes, 0, ch);
ch = fis.read(bytes, 0, BUFFER_SIZE);
}
}
else {
// file not found
String errorMessage = "HTTP/1.1 404 File Not Found\r\n" +
"Content-Type: text/html\r\n" +
"Content-Length: 23\r\n" +
"\r\n" +
"<h1>File Not Found</h1>";
output.write(errorMessage.getBytes());
}
}
catch (Exception e) {
// thrown if cannot instantiate a File object
System.out.println(e.toString() );
}
finally {
if (fis!=null)
fis.close();
}
}
}
在上述的web的应用中,新建了一个ServerSocket,并且处于接受连接状态,他可以接收来自浏览器的请求:http://127.0.0.1/index.html后, 会在浏览器中显示对应的页面(需要在项目路径的webroot下面放入想显示的index.html页面),当接受http://127.0.0.1/SHUTDOWN 后,会关闭连接。