AJAX和跨域——从Java程序员的角度理解

18 篇文章 1 订阅
9 篇文章 0 订阅

前言:
最近遇到了ajax的跨域问题,让我觉得很是棘手。跨域,对我来说是一个熟悉又陌生的问题,熟悉是因为我听过几种解决方法,陌生是因为我自己从来没有主动处理过跨域的情况。而且,我其实并不会前端知识,只是了解一个大概,所以也就没什么机会尝试。即使遇到了,也因为不会前端的知识而放弃了。不过,因为学习了计算机网络的知识、HTTP的知识、Java网络编程,我觉得我可以站在我自己的角度深入探究一下这个问题了。

让我们先来看一个效果图:
注:每个按钮对应一种跨域解决方案。
在这里插入图片描述
这里可以先阅读这篇博客,先了解一下:HTTP首部——Content-Type的作用

这里的主要方法其实很简单,就是构造请求报文与响应报文。所以,最好需要先了解一下HTTP报文的结构,但是不了解其实也没关系,我觉得你甚至可以不看这篇文章的内容,我推荐直接把代码运行起来,先有一个直观的感受,然后跟着代码走,这样理解会更好一些,全部代码都已经贴出来了,不用下载的,有问题的话,也可也在评论区交流解决。
通过了解特殊的HTTP首部对跨域访问的影响,深入学习ajax中跨域问题,相信会对你有一个帮助,虽然我也是才开始了解。



跨域问题

所谓的域指的是当前页面所处的URL,通常包括:protocol、host/ip、端口。只要这三个有一个不同,即认为是两个不同的域。浏览器有一个同源策略,即不允许不同域之间互相访问对方。 它是一种安全策略,用来保护我们的上网安全的。

同源策略分为两种:
DOM 同源策略:禁止对不同源页面 DOM 进行操作。
XMLHttpRequest 同源策略:禁止使用 XHR 对象向不同源的服务器地址发起 HTTP 请求。

这里我们主要讨论 XMLHttpRequest的同源策略,即AJAX技术的使用。

注:
对于带有src属性的标签(script、img、iframe标签)是不受同源策略限制的,因此通过向页面中动态添加标签来完成对跨域资源的访问,这就是jsonp的原理。这里我们也不讨论jsonp了,感觉用得比较少,可能是我见的太少了。

接下来我们主要讨论三种情况:
1.AJAX实现简单请求的直接跨域。
2.AJAX通过代理实现间接跨域。
3.AJAX实现复杂请求的直接跨域。



OK, let’s go!

我接下来会使用一些简单的java代码,来实现上述三种跨域的情况。希望通过这些代码,来加深对于ajax的理解,如果以后学习前端也会方便很多。

AJAX1.0是不允许跨域的,但是现在已经我们已经普遍使用ajax2.0。而ajax2.0是允许实现跨域的,不过它需要服务器的配合。

这里简单了解一下简单请求复杂请求

简单请求
1.请求方式只能是:GET、POST、HEAD
2.HTTP请求头限制一下字段:Accept、Accept-Language、Content-Language、Content-Type、Last-Event-ID
3.Content-type只能是下面三种:application/x-www-form-urlencoded、multipart/form-data、text/plain

对于简单请求,浏览器直接发起一个请求,然后在请求头中增加一个origin字段,用来说明本次请求的源。服务器根据这个值,来决定是否同意该请求,服务器返回的响应会多几个头信息字段三个与CORS请求相关,都是以Access-Control-开头。

Access-Control-Allow-Origin:必须字段,* 表示接受任意域名的请求,通常使用指定域名。
Access-Control-Allow-Credentials:可选字段,是个布尔值,表示是否可以携带cookie,
Access-Control-Allow-Headers:该字段可选,里面可以获取指定的头部字段。

注意:如果Access-Control-Allow-Origin字段设置,此字段设为true无效。*

复杂请求
对于不属于上面简单请求的基本上都属于复杂请求,对于复杂请求的处理会比较麻烦一些。它会首先发送一个 Preflighted Request (预检请求),它的请求方法为 OPTIONS。然后服务器会响应端一个报文,用来说明是否允许访问。如果允许访问,那么就会继续发送用户的请求。

具体的信息参考下面这篇博客,写得非常不错:浏览器同源策略及跨域的解决方法



AJAX实现简单请求的直接跨域

代码贴在下面了,我这里就是效果演示。
ajax的简单请求,需要服务器端返回一个首部:Access-Control-Allow-Origin。它的值是允许访问的源的地址,这里设置一个通配符,即允许所有的源访问,但是通常是需要指定特定的源的,这里只是一个测试,并不是从安全角度来看问题。

在这里插入图片描述
浏览器查看发送的ajax请求:
在这里插入图片描述

Response Header和Request Header信息:
注意:Request中的 Origin 和 Response 中的 Access-Control-Allow-Origin,它们是互相匹配的才可以允许访问。
在这里插入图片描述
如果不允许的话,那么你就会见到这个老朋友了!这是其它错误引起的跨域失败,不过红色部分描述的错误都是一样的:

Access to XMLHttpRequest at ‘xxx’ from origin ‘yyy’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.

在这里插入图片描述
这段异常信息表示:通过Ajax的跨域访问已经被跨域资源共享策略(CORS)封锁,原因是:没有允许该域的 Access-Control-Allow-Origin 首部出现在请求的资源里面,即响应头的位置。
所以,它就引出了一个问题的答案——Ajax的跨域检测是检测响应信息,即请求是可以正常发送的,但是如果发现不允许跨域的话,响应信息会被拦截。

并且,这里还有一个更有意思的地方:即localhost和127.0.0.1是属于不同域的,是不是很神奇。但是解释也很简单:虽然都指向的是本机,但是一个代表的是IPV6的地址,一个是IPV4的地址,这样当然属于不同域的了。其实这里涉及一个优先匹配原则,localhost是对应一个ipv4和ipv6地址的,但是现在的电脑似乎都是默认ipv6优先了,并且修改这个优先级也很麻烦的,而且localhost指向的ip地址也是可以修改的!

localhost和127.0.0.1的区别
提高服务的不同源服务器打印输出信息

在这里插入图片描述

AJAX通过代理实现间接跨域

在这里插入图片描述

如果服务器不配合,即没有权限修改服务器,那么就可以使用代理转发的方式来实现间接跨域。即通过将请求(Request)发送给同源的服务器,然后由服务器自己通过网络向目标服务器发起请求,并返回得到的响应(Response)。这里注意:跨域访问,是浏览器层面的限制,如果绕过浏览器那么是不会有什么限制的。这个在前后端分离的项目中用的比较多,通常是使用Nginx代理转发实现。

在这里插入图片描述
这段代码的逻辑我是这样处理的:首先收到了请求,然后使用一个包装的Socket类,发送一个代理请求,获取到响应之后,为了方便处理我直接把整个响应原封不动的发给请求地址了,这样就实现了一个代理转发的功能了,虽然有点简陋,不过原理应该是没有错的。

访问地址:127.0.0.1:9000/proxy
代理地址:127.0.0.1:9002/fu

这里就是相当于访问地址映射到了代理地址,不过它总共是需要两次请求的。而简单请求只需要一次,速度上是有一些优势的。但是也要考虑到,通过代理的方式具有很大的优势,它只需要在自己这端做出修改即可,如果请求的后端服务并不是自己的,那么应该只能使用这种方法了。

	String proxyHost = "127.0.0.1";
	int proxyPort = 9002;
	ProxySocket proxy = new ProxySocket(proxyHost, proxyPort);
	String response = proxy.proxyRequest("/fu");   // 代理的请求路径是RestServer的根路径
	body = response.getBytes(StandardCharsets.UTF_8);
	// 这里我偷一个懒,我直接把响应报文返回,不再去解析它来获取报文体
	out.write(body);  // 这个body,实际上是整个报文
	out.flush();

虽然是两次请求,但是浏览器只能看到一个,因为只有http://127.0.0.1:9000/proxy是经过浏览器的,代理转发是绕过浏览器的,正因为如此,才可以实现跨域访问。

在这里插入图片描述

AJAX实现复杂请求的直接跨域

在这里插入图片描述

对于非简单请求,就认为是复杂请求。具体的定义可以看上面推荐的那篇博客,这里主要重在验证方面,因为他们介绍的已经很详细了。
复杂请求分为两步:
1.预检请求 Preflighted Request
2.真正请求

预检请求是一个OPTIONS请求(和GET请求、POST请求一样,但是请求方法不同),下面是关于OPTIONS请求的一个简要介绍:

OPTIONS 方法请求 Web 服务器告知其支持的各种功能。 可以询问服务器通常支持哪些方法, 或者对某些特殊资源支持哪些方法。(有些服务器可能只支持对一些特殊类型的对象使用特定的操作)。这为客户端应用程序提供了一种手段,使其不用实际访问那些资源就能判定访问各种资源的最优方式。

因此,虽然复杂请求也会是两次HTTP请求,但是它应该在速度上也是快于代理转发的。下面是我的观点(可能不正确!):
1.OPTIONS请求很轻量。
2.通过浏览器发送的请求默认使用的是长连接,所以只需要建立一次连接即可。而转发请求中,转发过程必须建立一次HTTP连接,所以就是两次连接了。
3.这里的OPTIONS请求也不是一定会发送的,它是收到一个首部控制的,即Access-Control-Max-Age,这个首部的值表示间隔多久之后再次发送OPTIONS请求。例如我这里使用的是:Access-Control-Max-Age: 60。它表示是每隔60秒发送一次。所以,大部分时候浏览器发送的还是一次请求,因为通常设置都是要大于60秒的。

复杂请求方式的两次请求
注意看类型中标注xhr的才是ajax请求,这说明第一次并不是真实的请求,我们打开它看一下。
在这里插入图片描述

这里注意看一下几点:
请求方法:OPTIONS
请求头:我使用了POST方法,并且添加了自定义首部 X-dragon。所以OPTIONS请求中会询问是否接受POST请求和x-dragon首部。
响应头:允许X-dragon首部,允许POST方法,允许跨域访问。
上面这些都要匹配到了,整个才算是成功。不然,即使返回的状态码是200 OK,也是会被拦截的。
注意:这个请求是浏览器发送的,所以你看不到我的自定义首部!它在接下来的真正的请求中呢。
在这里插入图片描述
真正的请求
在这里插入图片描述

完整代码

这里这个 CheckServer.java 是我验证一个错误用的,不用管它。我只是介绍其它的类的作用:
FileServer.java 和 ProxySocket.java 是一个静态的web服务器,它的效果很简单,就是将index.html作为报文体返回。ProxySocket是一个Socket的简单包装类,用于发送请求报文并返回响应的报文。
RestServer.java 是一个提供json数据的静态的web服务器,它只会返回《凤求凰(节选)》和《关雎》两篇文章。

注意:两个服务器都会返回404,但是这里是一个没有用的功能,我只是希望服务器可以逻辑自洽,但是没有怎么处理未知的请求,因为它们虽然是返回404 Not Found页面,但是状态码还是200,哈哈!

这里最好开两个控制台启动FileServer和RestServer,因为每个类都会打印输出一些信息可以查看。但是我喜欢在IDE中启动FileServer,在命令行中启动RestServer,感觉也是很好的方法。不过,就是每次修改后,重写编译启动麻烦一些,没有在IDE中直接修改方便。

在这里插入图片描述

项目目录
在这里插入图片描述


favicon.ico
在这里插入图片描述


img.jpg在这里插入图片描述


index.html
scipt脚本中定义了三个ajax函数,它们分别是:
1.xhr2GetDat():ajax2.0简单请求的跨域。
请求通过浏览器直接发送给不同源的服务器,需要服务器匹配允许该源访问,即Access-Control-Allow-Origin支持。

2.proxyGetData():通过代理转发实现跨域。
请求通过浏览器直接发送给自己,然后自己再转发请求,不需要对方服务器支持。

3.complexXhr2GetData():ajax2.0复杂请求的跨域。
发送请求前需要先发送一个OPTIONS请求询问是否允许请求,如果允许了,才会发送真正的请求。

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>CrazyDragon</title>
		<script>
			function xhr2GetData() {
				var xhr = new XMLHttpRequest();
				xhr.timeout = 3*1000;
				xhr.onreadystatechange = function() {
					if (xhr.readyState == 4 && xhr.status == 200) {
						var data = JSON.parse(xhr.responseText);
						var poem = data["fu"].split("。");
						document.getElementById("content_xhr2").innerHTML = poem.join("。<br/>");
					}
				}
				xhr.open("GET","http://127.0.0.1:9002/fu", true);
				xhr.send();
			}
			
			function proxyGetData() {
				var xhr = new XMLHttpRequest();
				xhr.timeout = 3*1000;
				xhr.onreadystatechange = function() {
					if (xhr.readyState == 4 && xhr.status == 200) {
						var data = JSON.parse(xhr.responseText);
						var poem = data["fu"].split("。");
						document.getElementById("content_proxy").innerHTML = poem.join("。<br/>");
					}
				}
				// 先发送到自己的服务器,然后通过本服务器代理转发请求,得到响应再返回给调用,以此来间接达到跨域目的。
				xhr.open("GET","http://127.0.0.1:9000/proxy", true);
				xhr.send();
			}
			
			function complexXhr2GetData() {
				var xhr = new XMLHttpRequest();
				xhr.timeout = 3*1000;
				xhr.onreadystatechange = function() {
					if (xhr.readyState == 4 && xhr.status == 200) {
						var data = JSON.parse(xhr.responseText);
						var poem = data["poem"].split("。");
						document.getElementById("complex_content_xhr2").innerHTML = poem.join("。<br/>");
					}
				}
				xhr.open("POST", "http://127.0.0.1:9002/poem", true);
				xhr.setRequestHeader("X-dragon", "I love you yesterday and today!");
				xhr.send();
			}
		</script>
		
		<style type="text/css">
			body {
			    background-image: url('/bgImage');
			    background-repeat: no-repeat;
			    background-size: 100%;
			}
			#main {
				width: 900px;
				margin-top: 50px;
				margin-left: 10px;
				text-align: center;
				color: white;
				font-size: 20px;
				font-weight: bold;
			}
			.poem {
				float: left;
			}
			
			.poem div {
				width: 300px;
			}
			
		</style>
	</head>
	<body>
		<div id="main">
			<div class="poem">
			<h1>《凤求凰》</h1>
				<button type="button" onclick="xhr2GetData()">ajax2.0简单请求加载数据</button><br/><br/>
				<div id="content_xhr2">暂无数据!</div>
			</div>
			<div class="poem">
			<h1>《凤求凰》</h1>
				<button type="button" onclick="proxyGetData()">代理转发加载数据</button><br/><br/>
				<div id="content_proxy">暂无数据!</div>
			</div>
			<div class="poem">
			<h1>《关雎》</h1>
				<button type="button" onclick="complexXhr2GetData()">ajax2.0复杂请求加载数据</button><br/><br/>
				<div id="complex_content_xhr2">暂无数据!</div>
			</div>
		</div>
	</body>
</html>


简单的web服务器
通过Socket对象获取到请求信息,但是这里我只取第一行即请求行,它通常如下:

GET /bgImage HTTP/1.1

这里是三部分,即:请求方法类型、请求路径(可以带查询参数)、协议和版本号。
我这里只是需要请求路径,其它都不重要了,因为都是简单的get请求。获取到了请求路径以后,就可以根据不同的请求路径,响应不同的消息了。

这里使用一个switch来处理不同的请求,这是一种简化的方法,或许应该用树进行处理,但是也没必要这么麻烦,简单就好!

它的效果如下:
1.整个html页面
2.网页的图标favicon.ico
3.网页的背景图片

在这里插入图片描述


package org.dragon;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class FileServer {
	private static final int PORT = 9000;
	public static final char CR = '\r';
    public static final char LF = '\n';
    public static final String CRLF = "\r\n";
	public static final String BLANK = " ";
	
	public static void main(String[] args) {
		try (ServerSocket server = new ServerSocket(PORT)) {
			while (true) {
				System.out.println("等待连接。。。");
				Socket client = server.accept();
				new Thread(()-> {
					try {
						InputStream in = new BufferedInputStream(client.getInputStream());
						OutputStream out = new BufferedOutputStream(client.getOutputStream());
						StringBuilder sb = new StringBuilder();
						while (true) {
							int c = in.read();
							if (c == '\r' || c == '\n' || c == -1) break;
							sb.append((char)c);
						}
						
						String requestLine = sb.toString();
						String requestPath = null;
						
						if (requestLine.length() != 0) {
							System.out.println(requestLine);
							String[] requests = requestLine.split(BLANK);
							requestPath = requests[1];  // 只需要获取到请求路径即可。
						} else {
							// 这里似乎会有一个网络上面的问题,会有一个无效的请求,不处理它。
							return ;
						}
						
						byte[] body = null;
						String content_type = null;
						// 请求路由分发
						switch (requestPath) {
						case "/":
						case "/index.html": 
							Path path = Paths.get("./", "src", "org", "dragon", "index.html");
							// 响应体
							body = Files.readAllBytes(path);
							content_type = "text/html";
							break;
						case "/favicon.ico": 
							path = Paths.get("./", "src", "org", "dragon", "favicon.ico");
							// 响应体
							body = Files.readAllBytes(path);
							content_type = "image/x-icon";
							break;
						case "/bgImage": 
							path = Paths.get("./", "src", "org", "dragon", "img.jpg");
							// 响应体
							body = Files.readAllBytes(path);
							content_type = "image/jpeg";
							break;
						case "/proxy":
							String proxyHost = "127.0.0.1";
							int proxyPort = 9002;
							ProxySocket proxy = new ProxySocket(proxyHost, proxyPort);
							String response = proxy.proxyRequest("/fu");   // 代理的请求路径是RestServer的根路径
							body = response.getBytes(StandardCharsets.UTF_8);
							// 这里我偷一个懒,我直接把响应报文返回,不再去解析它来获取报文体
							out.write(body);  // 这个body,实际上是整个报文
							out.flush();
							return ;
						default: 
							response = "<h1>404 Not Found!</h1><br/>I love you yesterday and today!";
							body = response.getBytes(StandardCharsets.UTF_8);
							content_type = "text/html;charset=utf-8";
							break;
						}
						
						
						// 构造响应头
						StringBuilder headerBuilder = new StringBuilder();
						headerBuilder.append("HTTP/1.1 200 OK").append(CRLF)
						.append("Host:").append(BLANK).append(client.getInetAddress().getHostName()).append(CRLF)
						.append("Content-Type:").append(BLANK).append(content_type).append(CRLF)
						.append("Content-Length:").append(BLANK).append(body.length).append(CRLF)
						.append(CRLF);
						
						// 输出响应头
						System.out.println(headerBuilder);
						
						// 响应头
						byte[] header = headerBuilder.toString().getBytes(StandardCharsets.UTF_8);
						
						// 返回响应报文
						out.write(header);
						out.write(body);
						// 手动刷新输出流,这是必要的,否则很容易造成程序阻塞!
						out.flush();
						
						System.out.println("发送响应成功!");
					} catch (IOException e) {
						e.printStackTrace();
					} finally {
						try {
							client.close();
						} catch (IOException e) {
							e.printStackTrace();
						}
					}
				}).start();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}


代理转发请求工具类
这个类就是简单包装了一下Socket对象,通过它来发送一个请求报文,然后返回得到的响应报文。简单来说,就是一个输出一个输入。

package org.dragon;

import static org.dragon.FileServer.BLANK;
import static org.dragon.FileServer.CRLF;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.charset.StandardCharsets;

public class ProxySocket {
	private String host;
	private int port;
	
	public ProxySocket(String host, int port) {
		this.host = host;
		this.port = port;
	}
	
	public String proxyRequest(String requestPath) {
		String msg = null;
		try (Socket proxy = new Socket(host, port)) {
			System.out.println("使用Socket转发请求");
			InputStream in = new BufferedInputStream(proxy.getInputStream());
			OutputStream out = new BufferedOutputStream(proxy.getOutputStream());
			
			// 构造响应头
			StringBuilder headerBuilder = new StringBuilder();
			headerBuilder.append("GET").append(BLANK).append(requestPath).append(BLANK).append("HTTP/1.0").append(CRLF)
			.append("Host:").append(BLANK).append(host).append(CRLF)
			.append("User-Agent").append(":").append(BLANK).append("Alfred's Server").append(CRLF)
			.append(CRLF);
			
			// web 上的 get 请求方式,通常没有请求体,但是并不是不能有的。
			// 发送请求
			byte[] header = headerBuilder.toString().getBytes();
			out.write(header);
			out.flush();
			
			// 接受响应
			byte[] b = new byte[1024];
			int len = in.read(b);
			msg = new String(b, 0, len, StandardCharsets.UTF_8);
		} catch (IOException e) {
			e.printStackTrace();
		}
		return msg;
	}
}

提高服务的不同源服务器
这个简单服务器的功能和上面的类似,不过它的功能更加简单了,它连图标都没有,上面那个我还是加了图标的。唯一需要注意的是,我在 /poem 请求中单独增加了对于 OPTIONS 请求的处理,返回了OPTIONS 请求需要的信息,否则这个OPTIONS即使成功返回也是不行的,缺少了必要的控制信息,浏览器是不会发送第二次请求的。

package org.dragon;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;

public class RestServer {
	private static final int PORT = 9002;
	public static final char CR = '\r';
    public static final char LF = '\n';
    public static final String CRLF = "\r\n";
	public static final String BLANK = " ";
	private static String fu = "有一美人兮,见之不忘。"
							   + "一日不见兮,思之如狂。"
							   + "凤飞翱翔兮,四海求凰。"
							   + "无奈佳人兮,不在东墙。"
							   + "将琴代语兮,聊写衷肠。"
							   + "何日见许兮,慰我彷徨。"
							   + "愿言配德兮,携手相将。" 
							   + "不得於飞兮,使我沦亡。";
	
	private static String poem = "关关雎鸠,在河之洲。"
							   + "窈窕淑女,君子好逑。"
							   + "参差荇菜,左右流之。" 
							   + "窈窕淑女,寤寐求之。" 
							   + "求之不得,寤寐思服。" 
							   + "悠哉悠哉,辗转反侧。" 
							   + "参差荇菜,左右采之。" 
							   + "窈窕淑女,琴瑟友之。" 
							   + "参差荇菜,左右芼之。" 
							   + "窈窕淑女,钟鼓乐之。";
	
	public static void main(String[] args) {
		try (ServerSocket server = new ServerSocket(PORT)) {
			while (true) {
				System.out.println("等待连接中...");
				Socket client = server.accept();
				new Thread(()-> {
					try {
						InputStream in = new BufferedInputStream(client.getInputStream());
						OutputStream out = new BufferedOutputStream(client.getOutputStream());
						StringBuilder sb = new StringBuilder();
						while (true) {
							int c = in.read();
							if (c == '\r' || c == '\n' || c == -1) break;
							sb.append((char)c);
						}
						String requestLine = sb.toString();
						
						// 打印剩下所有的请求行信息,用于查看!
						byte[] b = new byte[1024];
						int len = in.read(b);
						System.out.println(new String(b, 0, len, StandardCharsets.UTF_8));
						
						String requestMethod = null;
						String requestPath = null;
						
						if (requestLine.length() != 0) {
							System.out.println(requestLine);
							String[] requests = requestLine.split(BLANK);
							requestMethod = requests[0];
							requestPath = requests[1];  // 只需要获取到请求路径即可。
						} else {
							// 这里似乎会有一个网络上面的问题,会有一个无效的请求,不处理它。
							return ;
						}
						
						
						byte[] body = null;
						String response = null; 
						String content_type = null;
						// 路由分发 
						switch (requestPath) {
						case "/fu":
							response = "{\"fu\" : \"" + fu + "\"}";
							content_type = "text/plain;charset=utf-8";
							break;
						case "/poem":
							// 此处需要处理预检请求 preflighted request
							if ("OPTIONS".equals(requestMethod)) {
								// 这里我就简单处理了,直接构造一个报文返回即可。
								response = "HTTP/1.0 200 OK" + CRLF
										+ "Allow: GET, HEAD, POST, OPTIONS" + CRLF
										+ "Access-Control-Allow-Origin: *" + CRLF
										+ "Access-Control-Allow-Headers: X-dragon" + CRLF
										+ "Access-Control-Max-Age: 60" + CRLF  // 每隔60秒需要发送一次预检请求
										+ "Content-Length: 0"  + CRLF
										+ CRLF;
								body = response.getBytes(StandardCharsets.UTF_8);
								out.write(body);
								out.flush();
								// 直接返回,结束请求
								return ;
							} else {
								response = "{\"poem\" : \"" + poem + "\"}";
								content_type = "application/json;charset=utf-8";
							}
							break;
						default:
							response = "<h1>404 Not Found!</h1><br/>I love you yesterday and today!";
							content_type = "text/html;charset=utf-8";
							break;
						}
						
						// 响应体
						body = response.getBytes(StandardCharsets.UTF_8);
						
						// 构造响应头
						StringBuilder headerBuilder = new StringBuilder();
						headerBuilder.append("HTTP/1.1 200 OK").append(CRLF)
						.append("Server:").append(BLANK).append("Alfred's Server").append(CRLF)
						.append("Content-Type:").append(BLANK).append(content_type).append(CRLF)
						.append("Access-Control-Allow-Origin:").append(BLANK).append("*").append(CRLF)
						.append("Content-Length:").append(BLANK).append(body.length).append(CRLF)
						.append(CRLF);  // 别忘了,最后有一个 CRLF 用于分隔 header 和 body 两部分。
						
						System.out.println(headerBuilder);
						// 响应头
						byte[] header = headerBuilder.toString().getBytes(StandardCharsets.UTF_8);
						// 返回响应报文
						out.write(header);
						out.write(body);
						// 手动刷新输出流,这是必要的,否则很容易造成程序阻塞!
						out.flush();
					} catch (IOException e) {
						e.printStackTrace();
					} finally {
						try {
							client.close();
						} catch (IOException e) {
							e.printStackTrace();
						}
					}
				}).start();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

总结

这里使用java网络编程的知识来创建一个简单的实验性质的静态web服务器,总体来说还是很有意思的。这里的东西感觉和前端、后端都是相关的,并且特意选择这种简陋的实现,是因为我可以看到所有的请求和响应,这对于网络学习是很好的,因为这些东西都是见过的,毕竟也是正儿八经学习过了计算机网络的人了。总的来说,还是要多探索新的知识,ajax是一种技术,但是它是建立在计算机网络的知识之上的,所以掌握良好的基础也是很重要的!

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
本课程详细讲解了以下内容:    1.jsp环境搭建及入门、虚拟路径和虚拟主机、JSP执行流程    2.使用Eclipse快速开发JSP、编码问题、JSP页面元素以及request对象、使用request对象实现注册示例    3.请求方式的编码问题、response、请求转发和重定向、cookie、session执行机制、session共享问题     4.session与cookie问题及application、cookie补充说明及四种范围对象作用     5.JDBC原理及使用Statement访问数据库、使用JDBC切换数据库以及PreparedStatement的使用、Statement与PreparedStatement的区别     6.JDBC调用存储过程和存储函数、JDBC处理大文本CLOB及二进制BLOB类型数据     7.JSP访问数据库、JavaBean(封装数据和封装业务逻辑)     8.MVC模式与Servlet执行流程、Servlet25与Servlet30的使用、ServletAPI详解与源码分析     9.MVC案例、三层架构详解、乱码问题以及三层代码流程解析、完善Service和Dao、完善View、优化用户体验、优化三层(加入接口和DBUtil)    1 0.Web调试及bug修复、分页SQL(Oracle、MySQL、SQLSERVER)     11.分页业务逻辑层和数据访问层Service、Dao、分页表示层Jsp、Servlet     12.文件上传及注意问题、控制文件上传类型和大小、下载、各浏览器下载乱码问题     13.EL表达式语法、点操作符和括号操作符、EL运算、隐式对象、JSTL基础及set、out、remove     14.过滤器、过滤器通配符、过滤器链、监听器     15.session绑定解绑、钝化活化     16.以及Ajax的各种应用     17. Idea环境下的Java Web开发
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值