老规矩,先给个需求:浏览器向服务端提交一个GET请求,服务端向服务器响应一张图片,并展示在浏览器上。
在编写程序之前,首先分析一下浏览器做了什么,服务器又要做什么。
1、浏览器向服务器提交了一个GET请求的请求报文,请求的url是图片的路径。
2、服务器读到请求之后,拆解请求报文的首行,获得请求报文中的url,找到对应的图片响应给浏览器,并展示出来。
至于怎么用HTTP手写一个服务器可以参考我先前写的文章:
手写一个简易的HTTP服务器。
https://segmentfault.com/a/1190000043443755
1、浏览器向服务器提交了一个GET请求的请求报文,请求的url是图片的路径。
这里的填写自己服务器所存储图片的路径
localhost:12345/getPicture/src/webapp/1.png
2、服务器读到请求之后,拆解请求报文的首行,获得请求报文中的url,找到对应的图片。
public class Server {
/*
* 抛出异常
* */
private static class DemoApplication extends RuntimeException{
public DemoApplication() {
super();
}
public DemoApplication(String message) {
super(message);
}
public DemoApplication(String message, Throwable cause) {
super(message, cause);
}
public DemoApplication(Throwable cause) {
super(cause);
}
protected DemoApplication(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
/*
* 启动监听端口
* */
public Server(int port) throws IOException {
if (port < 1 || port >65535){
throw new DemoApplication("端口错误");
}
ServerSocket serverSocket = new ServerSocket(port);
ExecutorService pool = Executors.newFixedThreadPool(50);
System.out.println("已经启动,开始监听端口"+port);
while (true){
Socket clientSocket = serverSocket.accept();
if (!clientSocket.isClosed()){
//首先服务端输出内容到客户端的输入流
Runnable r = () -> {
try {
acceptToClient(clientSocket);
} catch (IOException e) {
throw new RuntimeException(e);
}
};
pool.submit(r);
}
}
}
private void acceptToClient(Socket clientSocket) throws IOException {
//使用 InputStream 来读取浏览器的请求报文
InputStream clientIn = clientSocket.getInputStream();
//使用 BufferedReader 将InputStream强转成 BufferedReader。(InputStream读取到的是字节,BufferedReader读取到的是字符,我们需要的是字符,所以强转成BufferedReader)
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(clientIn,"UTF-8"));
System.out.println("进入服务器");
OutputStream clientOut = clientSocket.getOutputStream();
String content = "";
String cookieName = "";
String cookieValue = "";
String cookieTime = "";
String type = "";
/*
* 读取第一行要进行特殊处理,从中获取到请求报文中的 方法 和 URL
* */
String firstLine = bufferedReader.readLine();
System.out.println("读取到的第一行是"+firstLine);
String requestUri = firstLine.split(" ")[1];
System.out.println("读取数组中的URL"+requestUri);
String method = firstLine.split(" ")[0];
System.out.println("这次请求的方法是"+method);
int length = 0;
/*
* 读取图片
* */
System.out.println(requestUri.split("/")[1]);
if(requestUri.split("/")[1].equals("getPicture"))
if(method.equals("GET")){
System.out.println("进入update方法");
int a = 1;
type ="bytes";
String path = requestUri.split("/")[3];//webapp
System.out.println(path);
if(!path.equals("webapp")){
System.out.println("访问的图片不存在");
clientOut.close();
return;
}
//lastIndexOf 返回此字符串中指定子字符串的最后一次出现的索引。空字符串""的最后一次出现被认为出现在索引值
//substring 返回子字符串,子字符串从索引处开始,扩展到末尾
String fileName = requestUri.substring(requestUri.lastIndexOf("/") + 1);//1.png
System.out.println(fileName);
InputStream inputStream = new FileInputStream("src/webapp/" +fileName);
byte[] bytes = new byte[1024];
int len = 0;
clientOut.write("HTTP/1.1 200 OK\r\n".getBytes());//响应成功的响应头
clientOut.write("content-type:image/jpeg\r\n".getBytes());//告诉浏览器,我这边返回的是一个图片
clientOut.write("\r\n".getBytes());
while ((len = inputStream.read(bytes)) != -1) {
clientOut.write(bytes,0,len);//图片的内容
}
System.out.println("执行成功");
}
//判断是否带路径搜索,没带路径的默认搜索ajax-demo
String resourcePath = requestUri.equals("/") ? "ajax-demo" : requestUri.substring(1);
System.out.println(resourcePath);
//打印获取的html
String a = "";
String htmlStr = "";
try {
BufferedReader in = new BufferedReader(new FileReader("src/webapp/"+resourcePath +".html"));
while ((a = in.readLine()) != null) {
htmlStr =htmlStr + "\n" +a;
}
} catch (IOException e) {
System.out.print("错误");
}
//读取资源的内容
System.out.println(htmlStr);
//找不到资源直接返回404
if (htmlStr == null){
writeToClient(clientOut,"text/html",cookieName,cookieValue,cookieTime,404,"Not Found","<h1>404 FILE NOT FOUND</h1>",type,length);
return;
}
content = htmlStr;
writeToClient(clientOut,"text/html",cookieName,cookieValue,cookieTime,200,"OK",content,type,length);
}
/*
* 封装响应报头(报文)
* */
private void writeToClient(OutputStream clientOut, String ContentType ,String cookieName,String cookieValue,String cookieTime, int responseCode, String responseDes, String content,String type,int length) throws IOException {
clientOut.write(("HTTP/1.1 "+responseCode+ " " +responseDes+ "\r\n").getBytes());
clientOut.write(("Date: " + (new Date()).toString() + "\r\n").getBytes());
clientOut.write(("Content-Type: "+ ContentType +"; charset=UTF-8\r\n").getBytes());
clientOut.write(("Set-Cookie: "+cookieName+"="+cookieValue+";"+"max-age="+cookieTime+"\r\n").getBytes());
clientOut.write(("Accept-Ranges: "+type+"\r\n").getBytes());
clientOut.write(("Content-Length: "+length+"\r\n").getBytes());
clientOut.write("\r\n".getBytes());//空行
clientOut.write(content.getBytes());
clientOut.flush();
clientOut.close();
}
public static void main(String[] args) throws IOException {
new Server(12345);
}
}
执行试试!
执行成功!
那能不能每隔提交同一个url来请求服务器,获取到新的图片。
咱来试试。
也就是说,每刷新一次,获取图片的路径就要换一个,但要求提交的url是同一个。没错,通过更改服务器获取到的图片的路径来改变获取到的图片。
public class Server {
//这里多定义了一个全局变量
int i =1 ;//<<<<<<<这里多定义了一个全局变量
/*
* 抛出异常
* */
private static class DemoApplication extends RuntimeException{
public DemoApplication() {
super();
}
public DemoApplication(String message) {
super(message);
}
public DemoApplication(String message, Throwable cause) {
super(message, cause);
}
public DemoApplication(Throwable cause) {
super(cause);
}
protected DemoApplication(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
/*
* 启动监听端口
* */
public Server(int port) throws IOException {
if (port < 1 || port >65535){
throw new DemoApplication("端口错误");
}
ServerSocket serverSocket = new ServerSocket(port);
ExecutorService pool = Executors.newFixedThreadPool(50);
System.out.println("已经启动,开始监听端口"+port);
while (true){
Socket clientSocket = serverSocket.accept();
if (!clientSocket.isClosed()){
//首先服务端输出内容到客户端的输入流
Runnable r = () -> {
try {
acceptToClient(clientSocket);
} catch (IOException e) {
throw new RuntimeException(e);
}
};
pool.submit(r);
}
}
}
private void acceptToClient(Socket clientSocket) throws IOException {
//使用 InputStream 来读取浏览器的请求报文
InputStream clientIn = clientSocket.getInputStream();
//使用 BufferedReader 将InputStream强转成 BufferedReader。(InputStream读取到的是字节,BufferedReader读取到的是字符,我们需要的是字符,所以强转成BufferedReader)
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(clientIn,"UTF-8"));
System.out.println("进入服务器");
OutputStream clientOut = clientSocket.getOutputStream();
String content = "";
String cookieName = "";
String cookieValue = "";
String cookieTime = "";
String type = "";
/*
* 读取第一行要进行特殊处理,从中获取到请求报文中的 方法 和 URL
* */
String firstLine = bufferedReader.readLine();
System.out.println("读取到的第一行是"+firstLine);
String requestUri = firstLine.split(" ")[1];
System.out.println("读取数组中的URL"+requestUri);
String method = firstLine.split(" ")[0];
System.out.println("这次请求的方法是"+method);
int length = 0;
/*
* 读取图片
* */
System.out.println(requestUri.split("/")[1]);
if(requestUri.split("/")[1].equals("getPicture"))
if(method.equals("GET")){
System.out.println("进入update方法");
int a = 1;
type ="bytes";
String path = requestUri.split("/")[3];
System.out.println(path);
if(!path.equals("webapp")){
System.out.println("访问的图片不存在");
clientOut.close();
return;
}
//lastIndexOf 返回此字符串中指定子字符串的最后一次出现的索引。空字符串""的最后一次出现被认为出现在索引值
//substring 返回子字符串,子字符串从索引处开始,扩展到末尾
String fileName = requestUri.substring(requestUri.lastIndexOf("/") + 2);//.png 这里将索引的位置后移了一位
System.out.println(fileName);
i++;
//定义一共有多少张图片,到达数量返回到第一张
if(i>5){
i = 1;
}
InputStream inputStream = new FileInputStream("src/webapp/"+i +fileName);
byte[] bytes = new byte[1024];
int len = 0;
clientOut.write("HTTP/1.1 200 OK\r\n".getBytes());
clientOut.write("content-type:image/jpeg\r\n".getBytes());//告诉浏览器,我这边返回的是一个图片
clientOut.write("\r\n".getBytes());
while ((len = inputStream.read(bytes)) != -1) {
clientOut.write(bytes,0,len);
}
System.out.println("执行成功");
}
String resourcePath = requestUri.equals("/") ? "ajax-demo" : requestUri.substring(1);
System.out.println(resourcePath);
//打印获取的html
String a = "";
String htmlStr = "";
try {
BufferedReader in = new BufferedReader(new FileReader("src/webapp/"+resourcePath +".html"));
while ((a = in.readLine()) != null) {
htmlStr =htmlStr + "\n" +a;
}
} catch (IOException e) {
System.out.print("错误");
}
//读取资源的内容
System.out.println(htmlStr);
//找不到资源直接返回404
if (htmlStr == null){
writeToClient(clientOut,"text/html",cookieName,cookieValue,cookieTime,404,"Not Found","<h1>404 FILE NOT FOUND</h1>",type,length);
return;
}
content = htmlStr;
writeToClient(clientOut,"text/html",cookieName,cookieValue,cookieTime,200,"OK",content,type,length);
}
/*
* 封装响应报头(报文)
* */
private void writeToClient(OutputStream clientOut, String ContentType ,String cookieName,String cookieValue,String cookieTime, int responseCode, String responseDes, String content,String type,int length) throws IOException {
clientOut.write(("HTTP/1.1 "+responseCode+ " " +responseDes+ "\r\n").getBytes());
clientOut.write(("Date: " + (new Date()).toString() + "\r\n").getBytes());
clientOut.write(("Content-Type: "+ ContentType +"; charset=UTF-8\r\n").getBytes());
clientOut.write(("Set-Cookie: "+cookieName+"="+cookieValue+";"+"max-age="+cookieTime+"\r\n").getBytes());
clientOut.write(("Accept-Ranges: "+type+"\r\n").getBytes());
clientOut.write(("Content-Length: "+length+"\r\n").getBytes());
clientOut.write("\r\n".getBytes());//空行
clientOut.write(content.getBytes());
clientOut.flush();
clientOut.close();
}
public static void main(String[] args) throws IOException {
new Server(12345);
}
}
同一个url请求不同的图片也完成了,那能不能每隔10秒钟,同一个url才能请求到不同的图片。
隔一段时间图片的路径才进行变更,这时就可以用到 Spring Boot的定时任务:@Scheduled
表示每隔多久执行一次方法。如果@Scheduled(fixedDelay = 5000),那就是每隔5秒执行一次方法。我们来试试。
要想成功启用这个注解,需要Spring Boot的依赖注入。所以程序的启动入口就不能是Server的main()方法了,我们需要重新定义一个类作为程序的启动入口。
1、定义一个Application类作为程序的启动入口。
@SpringBootApplication
@EnableScheduling
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
@EnableScheduling 可确保创建后台任务执行程序(用来开启定时任务的)
2、再写一个类,作为定时任务
@Component
public class AddMethod {
//定义一个成员变量server
private Server server;
{
try {
server = new Server(12345);
System.out.println(server);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Scheduled(fixedDelay = 10000)
public void add(){
//调用server类的add()方法
server.add();
}
}
3、在Server类中添加add方法
public class Server {
int i =1 ;
/*
* 抛出异常
* */
int add(){
i++;
if(i>5){
i=1;
}
System.out.println("i="+i);
return i;
}
private static class DemoApplication extends RuntimeException{
public DemoApplication() {
super();
}
public DemoApplication(String message) {
super(message);
}
public DemoApplication(String message, Throwable cause) {
super(message, cause);
}
public DemoApplication(Throwable cause) {
super(cause);
}
protected DemoApplication(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
/*
* 启动监听端口
* */
public Server(int port) throws IOException {
if (port < 1 || port >65535){
throw new DemoApplication("端口错误");
}
ServerSocket serverSocket = new ServerSocket(port);
ExecutorService pool = Executors.newFixedThreadPool(50);
System.out.println("已经启动,开始监听端口"+port);
while (true){
Socket clientSocket = serverSocket.accept();
if (!clientSocket.isClosed()){
//首先服务端输出内容到客户端的输入流
Runnable r = () -> {
try {
acceptToClient(clientSocket);
} catch (IOException e) {
throw new RuntimeException(e);
}
};
pool.submit(r);
}
}
}
private void acceptToClient(Socket clientSocket) throws IOException {
//使用 InputStream 来读取浏览器的请求报文
InputStream clientIn = clientSocket.getInputStream();
//使用 BufferedReader 将InputStream强转成 BufferedReader。(InputStream读取到的是字节,BufferedReader读取到的是字符,我们需要的是字符,所以强转成BufferedReader)
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(clientIn,"UTF-8"));
System.out.println("进入服务器");
OutputStream clientOut = clientSocket.getOutputStream();
String content = "";
String cookieName = "";
String cookieValue = "";
String cookieTime = "";
String type = "";
/*
* 读取第一行要进行特殊处理,从中获取到请求报文中的 方法 和 URL
* */
String firstLine = bufferedReader.readLine();
System.out.println("读取到的第一行是"+firstLine);
String requestUri = firstLine.split(" ")[1];
System.out.println("读取数组中的URL"+requestUri);
String method = firstLine.split(" ")[0];
System.out.println("这次请求的方法是"+method);
int length = 0;
/*
* 读取图片
* */
System.out.println(requestUri.split("/")[1]);
if(requestUri.split("/")[1].equals("getPicture"))
if(method.equals("GET")){
System.out.println("进入update方法");
int a = 1;
type ="bytes";
String path = requestUri.split("/")[3];
System.out.println(path);
if(!path.equals("webapp")){
System.out.println("访问的图片不存在");
clientOut.close();
return;
}
//lastIndexOf 返回此字符串中指定子字符串的最后一次出现的索引。空字符串""的最后一次出现被认为出现在索引值
//substring 返回子字符串,子字符串从索引处开始,扩展到末尾
String fileName = requestUri.substring(requestUri.lastIndexOf("/") + 2);//1.png
System.out.println(fileName);
System.out.println(i);
InputStream inputStream = new FileInputStream("src/webapp/"+i +fileName);
System.out.println("src/webapp/"+ i +fileName);
byte[] bytes = new byte[1024];
int len = 0;
clientOut.write("HTTP/1.1 200 OK\r\n".getBytes());
clientOut.write("content-type:image/jpeg\r\n".getBytes());//告诉浏览器,我这边返回的是一个图片
clientOut.write("\r\n".getBytes());
while ((len = inputStream.read(bytes)) != -1) {
clientOut.write(bytes,0,len);
}
System.out.println("执行成功");
}
String resourcePath = requestUri.equals("/") ? "ajax-demo" : requestUri.substring(1);
System.out.println(resourcePath);
//打印获取的html
String a = "";
String htmlStr = "";
try {
BufferedReader in = new BufferedReader(new FileReader("src/webapp/"+resourcePath +".html"));
while ((a = in.readLine()) != null) {
htmlStr =htmlStr + "\n" +a;
}
} catch (IOException e) {
System.out.print("错误");
}
//读取资源的内容
System.out.println(htmlStr);
//找不到资源直接返回404
if (htmlStr == null){
writeToClient(clientOut,"text/html",cookieName,cookieValue,cookieTime,404,"Not Found","<h1>404 FILE NOT FOUND</h1>",type,length);
return;
}
content = htmlStr;
writeToClient(clientOut,"text/html",cookieName,cookieValue,cookieTime,200,"OK",content,type,length);
}
/*
* 封装响应报头(报文)
* */
private void writeToClient(OutputStream clientOut, String ContentType ,String cookieName,String cookieValue,String cookieTime, int responseCode, String responseDes, String content,String type,int length) throws IOException {
clientOut.write(("HTTP/1.1 "+responseCode+ " " +responseDes+ "\r\n").getBytes());
clientOut.write(("Date: " + (new Date()).toString() + "\r\n").getBytes());
clientOut.write(("Content-Type: "+ ContentType +"; charset=UTF-8\r\n").getBytes());
clientOut.write(("Set-Cookie: "+cookieName+"="+cookieValue+";"+"max-age="+cookieTime+"\r\n").getBytes());
clientOut.write(("Accept-Ranges: "+type+"\r\n").getBytes());
clientOut.write(("Content-Length: "+length+"\r\n").getBytes());
clientOut.write("\r\n".getBytes());//空行
clientOut.write(content.getBytes());
clientOut.flush();
clientOut.close();
}
}
执行看看,嚯,没效果。debug一下,看看发生了什么。
程序卡在 server = new Server(12345); 没有继续往下执行了,这是为啥?
/*
* 启动监听端口
* */
public Server(int port) throws IOException {
if (port < 1 || port >65535){
throw new DemoApplication("端口错误");
}
ServerSocket serverSocket = new ServerSocket(port);
ExecutorService pool = Executors.newFixedThreadPool(50);
System.out.println("已经启动,开始监听端口"+port);
while (true){
Socket clientSocket = serverSocket.accept();
if (!clientSocket.isClosed()){
//首先服务端输出内容到客户端的输入流
Runnable r = () -> {
try {
acceptToClient(clientSocket);
} catch (IOException e) {
throw new RuntimeException(e);
}
};
pool.submit(r);
}
}
}
因为为了监听浏览器发出的请求,在Server()方法中,我写了一个死循环来确保接收浏览的连接,所以这里要做出改动。
public Server(int port) throws IOException {
if (port < 1 || port >65535){
throw new DemoApplication("端口错误");
}
ServerSocket serverSocket = new ServerSocket(port);
ExecutorService pool = Executors.newFixedThreadPool(50);
System.out.println("已经启动,开始监听端口"+port);
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Socket clientSocket = serverSocket.accept();
if (!clientSocket.isClosed()) {
//首先服务端输出内容到客户端的输入流
Runnable r = () -> {
try {
acceptToClient(clientSocket);
} catch (IOException e) {
throw new RuntimeException(e);
}
};
pool.submit(r);
}} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
});
thread.start();
}
定义一个新的线程,就能解决这个问题。
再次执行,就能看到每隔10秒刷新,浏览就能收到请求到一张不一样的图片。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。