这周的任务太多了,服务器只学习了一半,先更新出来吧!下周继续!fighting!
一、HTML语法简介:
下面是一个网页的源码:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="Generator" content="EditPlus®">
<meta name="Author" content="">
<meta name="Keywords" content="">
<meta name="Description" content="">
<title>第一个表单</title>
</head>
<body>
<pre>
method:请求方式 get/post
get:默认方式,数据量小,安全性不高
post:量大,安全性相对高
action:请求的服务器路径
id:编号,前端区分唯一性,js中使用
name:名称,后端(服务器)区分唯一性,获取值
只要提交数据给后台,必须存在name
</pre>
<form method="get" action="http://localhost:8888/index.html">
用户名:<input type = "text" name="uname" id="uname"/>
密码:<input type="password" name="pwd" id="pwd"/>
兴趣:<input type="checkbox" name="fav" value="0">篮球
<input type="checkbox" name="fav" value="1">足球
<input type="checkbox" name="fav" value="2">乒乓球
<input type="submit" value="登录"/>
</form>
</body>
</html>
使用浏览器打开之后,显示的内容如下所示:
下面是两种不同的请求方式,服务器返回的响应信息:
1、post方式:
POST /index.html HTTP/1.1
Host: localhost:8888
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 38
Connection: keep-alive
Upgrade-Insecure-Requests: 1
2、get方式:
GET /index.html?uname=dasdsai&pwd=dsadfa&fav=0&fav=1&fav=2 HTTP/1.1
Host: localhost:8888
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: keep-alive
Upgrade-Insecure-Requests: 1
二、服务器的封装
http服务器的构建。在网络中的通信过程中,就是客户端和服务器端进行相互请求和应答方式的交互。客户端在自己这边输入服务器地址,请求获取服务器的资源信息。当服务器接收到请求信息的时候,根据已有的资源,进行响应的答复。所以在服务器的搭建过程中,主要就是将请求信息进行封装,并且进行分析,然后根据分析结果,把应答信息发送出去即可。
1、对响应信息进行封装
在对响应信息进行封装的过程中,主要的思想就是利用输出流,按照html语法格式,对应答信息进行包装,然后输出包装之后的信息块。
package com.peng.server.demo01;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.util.Date;
import com.peng.server.util.CloseUtil;
/**
* 封装响应信息
*
*/
public class Response {
//两个常量
private static String CRLF = "\r\n";//回车换行
private static String BLANK = " ";//空格
//流
BufferedWriter bw ;
//正文
private StringBuilder content;
//存储头信息
private StringBuilder headInfo;
//存储正文长度,正文长度是字节长度
private int len = 0;
//构造器
public Response() {
headInfo = new StringBuilder();
content = new StringBuilder();
len = 0;
}
public Response(Socket client) {
this();
try {
bw = new BufferedWriter(new OutputStreamWriter(client.getOutputStream()));
} catch (IOException e) {
headInfo = null;
}
}
public Response(OutputStream os) {
this();
bw = new BufferedWriter(new OutputStreamWriter(os));
}
/**
* 构建正文
*/
public Response print(String info) {
content.append(info);
len += info.getBytes().length;//不断改变正文长度
return this;
}
/**
* 构建正文+回车
*/
public Response println(String info) {
content.append(info).append(CRLF);
len += (info+CRLF).getBytes().length;//不断改变正文长度
return this;
}
/**
* 构建响应头
* 私有方法,仅供该类内部使用
*/
private void createHeadInfo(int code) {
//1) HTTP协议版本、代码状态、描述
headInfo.append("HTTP/1.1").append(BLANK).append(code).append(BLANK);
switch(code) {
case 200:
headInfo.append("OK");
break;
case 404:
headInfo.append("NOT FOUND");
break;
case 500:
headInfo.append("SERVER ERROR");
break;
};
headInfo.append(CRLF);
//2) 响应头(Response Head)
headInfo.append("Server:bjsxt Server/0.0.1").append(CRLF);
headInfo.append("Date:").append(new Date()).append(CRLF);
headInfo.append("Content-type:text/html;charset=GBK:").append(CRLF);
//长度:字节长度
headInfo.append("Content-Length:").append(len).append(CRLF);
headInfo.append(CRLF);//分隔符
}
//推送到客户端
void pushToClient(int code) throws IOException {
if(null == headInfo) {
code = 500;
}
createHeadInfo(code);
//头信息+分隔符
bw.append(headInfo.toString());
//正文
bw.append(content.toString());
bw.flush();
}
public void close() {
CloseUtil.closeAll(bw);
}
}
tips:
(1)、在html信息中,经常使用到换行和空格,所以我们预定义两个静态常量,使用的时候,直接进行调用。
(2)、在描述代码状态的时候,一般采用200代表正常状态,404代表服务器没有寻找到相关资源,500代表服务器报错。这些状态码属于一种常规操作,在编写服务器的时候,尽量使用常规定义,这样的话,在后续检查代码以及修正代码的时候,便于我们自己快速定位错误地方。
2、封装请求信息
package com.peng.server.demo01;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
/**
* 封装请求信息
*
*/
public class Request {
//请求方式
private String method;
//请求资源
private String url;
//请求参数
private Map<String,List<String>> parameterMapValues;
//内部
public static final String CRLF = "\r\n";
private InputStream is ;
private String requestInfo;//请求信息
//构造器
public Request() {
method = "";
url = "";
parameterMapValues = new HashMap<String,List<String>>();
}
public Request(InputStream is) {
this();
this.is = is;
try {
byte[] data = new byte[20480];
int len;
len = is.read(data);
requestInfo = new String(data,0,len);
} catch (IOException e) {
return;
}
//分析请求信息
parseRequestInfo();
}
/**
* 分析请求信息
*/
public void parseRequestInfo() {
if(null==requestInfo || ( requestInfo.trim().equals(" "))) {
return;
}
/**
* ===================================
* 从信息的首行分解出:请求方式 请求路径 请求参数(get可能存在)
* 如:GBT/index.html?name123&pwd=5467 HTTP/1.1
*
* 如果为post方式,请求参数可能在最后正文中
*
* ===================================
*/
//接收请求参数
String paramString ="";
//1、获取请求方式
String firstLine = requestInfo.substring(0, requestInfo.indexOf(CRLF));
int idx =requestInfo.indexOf("/");// /的位置
this.method = firstLine.substring(0, idx).trim();//获取请求的方式
String urlStr = firstLine.substring(idx,firstLine.indexOf("HTTP/")).trim();
if(this.method.equalsIgnoreCase("post")) {
this.url = urlStr;
paramString = requestInfo.substring(requestInfo.lastIndexOf(CRLF)).trim();
}else if(this.method.equalsIgnoreCase("get")) {
if(urlStr.contains("?")) {//是否存在参数
String[] urlArray = urlStr.split("\\?");
this.url = urlArray[0];
paramString = urlArray[1];//接收请求参数
}else {
this.url = urlStr;
}
}
//不存在请求参数
if(paramString.equals("")) {
return;
}
//2、将请求参数封装到MAP中
parseParams(paramString);
}
/**
* 将请求封装到MAP中
* @param paramString
*/
private void parseParams(String paramString) {
//分割 将字符串转成数组
StringTokenizer token = new StringTokenizer(paramString,"&");
while(token.hasMoreTokens()) {
String keyValue = token.nextToken();
String[] keyValues = keyValue.split("=");
if(keyValues.length==1) {
keyValues = Arrays.copyOf(keyValues, 2);
keyValues[1] = null;
}
String key = keyValues[0].trim();
String value = null==keyValues[1]?null:decode(keyValues[1].trim(),"gbk");
//转换成Map 分拣
if(!parameterMapValues.containsKey(key)) {
parameterMapValues.put(key, new ArrayList<String>());
}
List<String> values = parameterMapValues.get(key);
values.add(value);
}
}
/**
* 解决中文问题
* @param value
* @param code
* @return
*/
private String decode(String value,String code) {
try {
return java.net.URLDecoder.decode(value, code);
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
/**
* 根据页面的name获取对应的多个值
* @param name
* @return
*/
public String[] getParameterValues(String name) {
List<String> values = null;
if((values=parameterMapValues.get(name))==null) {
return null;
}else {
return values.toArray(new String[0]);
}
}
/**
* 根据页面的name获取对应的单个值
* @param name
* @return
*/
public String getParameter(String name) {
String[] values = getParameterValues(name);
if(null==values) {
return null;
}
return values[0];
}
//获取Url的方法
public String getUrl() {
return url;
}
}
tips:
(1)在进行获取相关值的时候,我们将其显示在网页上,将会涉及到不同编码集和解码集的转换。所以,为了避免解码集和编码集不同而导致的乱码问题,我们自定义一个指定相同解码集与编码集的方法。使用此方法,避免我们在显示时出现的乱码问题。
(2)在管理用户名和参数的问题上时,由于一个用户名可以对应有多个参数,比如密码,喜好等等,所以我们使用了MAP进行存储管理用户信息。键值对,键只有一个,我们使用用户名作为键,然后多个参数,我们使用链表进行存储,这样就可以很好的解决一个键对应多个值的问题。
3、服务器构建
package com.peng.server.demo01;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 创建服务器并启动
* 请求并响应
* @author 赵晓鹏
*
*/
public class Server4 {
private ServerSocket server;
private static String CRLF = "\r\n";//回车换行
private static String BLANK = " ";//空格
public static void main(String[] args) {
new Server4().start();
}
/**
* 启动方法
*/
public void start() {
try {
server = new ServerSocket(8888);
this.receive();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 接收客户端
*/
private void receive() {
try {
Socket client = server.accept();
//请求
Request req = new Request(client.getInputStream());
//响应
Response rep = new Response(client.getOutputStream());
//响应信息的构建
rep.println("<html><head><title>HTTP响应示例</title>");
rep.println("</head><body>");
rep.println("欢迎").println(req.getParameter("uname")).println("回来");
rep.println("</body></html>");
rep.pushToClient(200);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
tips:经过相应的封装后,我们在建立服务器的时候,只需要进行建立响应和请求类别就可以了,大大简化服务器建立时的代码。