拒绝BUG成功使用Socket实现http请求的响应与处理

核心内容

查看http响应报文:谷歌浏览器——>右键——>检查——>Network——>ALL——>Name

public int available() throws IOException

  • 返回从该输入流中可以读取(或跳过)的字节数的估计值,而不会被下一次调用此输入流的方法阻塞,下一次调用可能是同一个线程或另一个线程
  • 虽然InputStream的某些实现将返回流中的字节总数,但许多实现则不会
  • 使用此方法的返回值分配用于容纳此流中的所有数据的缓冲区是绝对不正确的
  • 在不阻塞的情况下可以从此输入流读取(或跳过)的字节数的估计;当到达输入流的末尾时为0

public InputStream getInputStream() throws IOException

  • 返回此套接字的输入流,如果此套接字具有相关联的通道,则所得到的输入流将其所有操作委派给通道

  • 如果通道处于非阻塞模式,则输入流的read操作将抛出IllegalBlockingModeException

报文信息

General(请求的url路径)
Request URL: http://localhost:10000/
Request Method: POST
Status Code: 200 OK
Remote Address: [::1]:10000
Referrer Policy: no-referrer-when-downgrade
    
Response Headers(服务器响应信息)
Access-Control-Allow-Headers: *
Access-Control-Allow-Origin: *
Connection: keep-alive
Content-Type: text/html; charset=utf-8
Keep-Alive: timeout=10
Transfer-Encoding: chunked
    
Request Headers(请求头)    
Provisional headers are shown
Accept: text/plain, */*; q=0.01
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Origin: http://127.0.0.1:8848
Referer: http://127.0.0.1:8848/JqueryTest/socket_index.html
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36

Form Data(请求参数)
action: A

报文响应内容

//	设置Content-Length方式	
	response.append("HTTP/1.1 200 OK\r\n");
	response.append("Access-Control-Allow-Origin:"+this.allowOrigin+"\r\n");
	response.append("Access-Control-Allow-Credentials:true\r\n");
	response.append("Access-Control-Allow-Headers:*\r\n");
	response.append("Content-Type: text/html; charset=utf-8\r\n");
	response.append("Connection: keep-alive\r\n");
	response.append("Keep-Alive: timeout=30\r\n");
	//内容
	String info = this.resultMessage;
	//Content-Length表明了实体主体部分的大小,不能大也不能小,单位是字节
	response.append("Content-Length:"+info.getBytes("utf-8").length+"\r\n\r\n");
	//头部信息完后用"\r\n\r\n"分隔,然后正文内容
	response.append(info);

//	设置Transfer-Encoding方式	
	response.append("HTTP/1.1 200 OK\r\n");
	response.append("Access-Control-Allow-Origin:"+this.allowOrigin+"\r\n");
//	response.append("Access-Control-Allow-Origin:http://127.0.0.1:8848\r\n");
	response.append("Access-Control-Allow-Credentials:true\r\n");
	response.append("Access-Control-Allow-Headers:*\r\n");
//	response.append("Accept-Ranges:bytes\r\n");
	response.append("Content-Type: text/html; charset=utf-8\r\n");
	response.append("Connection: keep-alive\r\n");
	response.append("Keep-Alive: timeout=30\r\n");
	response.append("Transfer-Encoding:chunked\r\n\r\n");
	String info = this.resultMessage;
//	报文分段,字节大小占用一行,然后具体内容占用一段,注意"\r\n"
	response.append(Integer.toHexString(info.getBytes().length)+"\r\n");
	response.append(info+"\r\n");
//	结尾标识
	response.append("0\r\n\r\n");

具体实现

html源码

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<script>
		// Get请求方式
		function sendGet(param) {
			var url = "http://127.0.0.1:8080/queryid";
			if(param!=null){
				url +="?"+param;
			}
			// 参数需要转义
			url = encodeURI(url);
			// 创建XMLHttpRequest
			var req = new XMLHttpRequest();
			// 创建与服务器的连接
			req.open("GET", url, true);
			// 返回结果的处理函数
			req.onreadystatechange = function() {
				if (req.readyState == 4 && req.status == 200) {
					var tip = document.getElementById("tip");
					var res = "<pre>";
					res +=req.responseText;
					// res += decodeURIComponent(req.responseText);
					res += "</pre>";
					tip.innerHTML = res;
	
				}
			};
			// 发生错误的处理函数
			req.onerror = function(){alert("请求错误");};
			// 设置跨域请求提供凭据信息
			req.withCredentials = true;
			req.send();
		}
		function sendPost(param){
			var url = "http://127.0.0.1:8080/querycontent";
			var req = new XMLHttpRequest();
			req.open("POST",url,true);
			req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
			req.onreadystatechange = function() {
				if (req.readyState == 4 && req.status == 200) {
					var tip = document.getElementById("tip");
					var res = "<pre>";
					// res += req.responseText;
					
					// res += decodeURIComponent(req.responseText);
					res += req.responseText;
					res += "</pre>";
					tip.innerHTML = res;
					
				}
			};
			// req.withCredentials = true;
			req.onerror = function(){alert("请求错误");};
			req.send(param);
		}
		function dealRequest(obj){
			var elem = document.getElementsByClassName("socketName");
			var name=elem[obj].getAttribute("name");
			// alert(typeof name);
			var param = null;
			if(elem[obj].value!=""){
				param = name;
				param +="=";
				param += elem[obj].value;
				param +="&msg=附加信息";
			}
			if(obj==0){
				sendPost(param);
			}else{
				sendGet(param);
			}
		}


</script>
<p>查询方式&nbsp;
查找内容:<input type="text" value="Java" placeholder="找关键字:如java" class="socketName" name="content"> 
<input type="button" onclick="dealRequest(0)" value="查询">
ID编号:<input type="number" value="101" placeholder="101~107" class="socketName" name="id">
<input type="button" onclick="dealRequest(1)" value="查询">
<div id="tip" style="color:blue"></div>
</body>
</html>

java源码

package socket;

import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.IOException;

import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URLEncoder;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface RequestMapping {
	String name() default "other";

	String value() default "没有请求对应的处理方法,回复默认请求消息";
}

/**
 * @author wonzeng
 * 2020年7月13日
 */
class MyController {
	HashMap<String, Object> mp = new HashMap<String, Object>();

	public MyController() {
		mp.put("101", "Java实例内部类、匿名内部类、静态内部类和局部内部类使用方法");
		mp.put("102", "Java关于向上转型与向下转型的区别");
		mp.put("103", "Java关于final关键字的使用方法");
		mp.put("104", "Java匿名对象与对象引用");
		mp.put("105", "Java抽象方法与抽象类和接口的区别");
		mp.put("106", "Java关于静态代码块、构造代码块和局部代码块的区别");
		mp.put("107", "Java关于修饰符public、private和protected的访问权限");
		System.out.println("MyController.MyController()执行了构造方法");
	}

	@RequestMapping
	public String defaultRequest() {
		return "<p style=\"color:blue\">没有查询到相关内容,请求时间:"+LocalDateTime.now().toString()+"</p>";
	}

//	根据id查找
	@RequestMapping(name = "/queryid")
	public String queryById(Map<String, Object> param) {
		String temp = (String) mp.getOrDefault(param.get("id"), "");
		if (temp.equals("")) {
			return defaultRequest();
		}
		return temp;
	}

//	根据content查找
	@RequestMapping(name = "/querycontent")
	public String queryByContent(Map<String, Object> param) {
		String str = param.get("content").toString();
		Collection<Object> lst = mp.values();
		StringBuilder builder = new StringBuilder("");
		lst.forEach((e) -> {
			String p = e.toString();
			if (p.toLowerCase().contains(str.toLowerCase())) {
				builder.append(p + "\r\n");
			}
		});
		if (builder.toString() != "") {
			return builder.toString();
		} else {
			return defaultRequest();
		}

	}

}

/**
 * @author wonzeng
 * 2020年7月13日
 */
public class HttpService {

	private int port = 8080;
	private int threadSize = 5;
	private ServerSocket serverSocket = null;
	private ExecutorService fixedPool = null;

	public static void main(String[] args) {
		new HttpService();
	}
//测试内容	
//	public static void main(String[] args) throws InstantiationException, IllegalAccessException, UnsupportedEncodingException, NoSuchMethodException, SecurityException {
		MyController p = new MyController();
//		Class cls = MyController.class;
//		MyController cont = (MyController) cls.newInstance();
//		Method []methods = cls.getDeclaredMethods();
//		String res = "";
//		HashMap<String,Object> paramObj =new  HashMap<String,Object>();
//		String noteName="/querycontent";
//		paramObj.put("content", "");
//		for(Method md:methods) {
//			
//			if(md.isAnnotationPresent(RequestMapping.class)) {
//				RequestMapping req = md.getAnnotation(RequestMapping.class);
				读取类上的注解名称
//				if(req.name().equals(noteName)) {
					InvocationTargetException
//					try {
//						System.out.println("方法名称:"+md.getName());
//						res = (String) md.invoke(cont, paramObj);
//						System.out.println("执行结果:"+res);
//					}catch(InvocationTargetException e) {
//						System.out.println("HttpService.StreamProcess.controllerTool()==========================");
						continue;
//						
//					}
//				}
//			}
//		}
		没有找到对应的方法
//		if(res.equals("")) {
//			Method md = cls.getMethod("defaultRequest", null);
			使用注解的默认值
//			RequestMapping mp = md.getAnnotation(RequestMapping.class);
//			res = mp.value();
//			
//		}
//		String info = URLEncoder.encode(res, "UTF-8");
//		System.out.println("HttpService.main():"+info);
		new HttpService();
//	}

	/**
	 * @author wonzeng
	 * 2020年7月13日
	 */
	public class StreamProcess implements Runnable {
		private Socket socket = null;

		private String requestHead = "";
		private HashMap<String, Object> params = null;
		private String url = null;
		private String resultMessage = null;
		private MyController cont = null;
		private String allowOrigin = "http://127.0.0.1:8848";
		public StreamProcess(Socket socket) {
			this.socket = socket;
			this.params = new HashMap<String, Object>();
		}

		@Override
		public void run() {
			readRequestStream();
			responseStream();
		}

//		读取请求头信息
		public void readRequestStream() {
			try {
				InputStream in = socket.getInputStream();
				int byteSize = in.available();
				byte[] arr = new byte[byteSize];
				in.read(arr);
				String msg = new String(arr, "utf-8");
				if (msg.length() > 0) {
					this.requestHead = msg;
					System.out.println("开始解析头部信息!");
//				解析头部信息
					parseHeaders(this.requestHead);
//				回复内容
					controllerTool();
				} else {
					System.out.println("请求头部信息为空!");
				}
			} catch (IOException e) {
				System.out.println(e);
			}
		}

//		反射读取注解,调用对应方法,返回字符串
		@SuppressWarnings("unused")
		private void controllerTool() {
			try {
				String noteName = "";
//			请求没有指定路径或只有 "/",没有请求参数
				if (!this.url.startsWith("/") || this.url.equals("/")) {
					noteName = "defaultRequest";
				} else {
//					有指定请求比如 /query
					noteName = this.url.substring(this.url.lastIndexOf("/"));
//					请求参数为空,指定方法不能执行,否则空指针异常
					if(this.params.size()==0) {
						noteName = "defaultRequest";
					}
				}
				System.out.println("请求方法名:" + noteName);
				Class<MyController> cls = MyController.class;
				if (cont == null) {
					cont = cls.newInstance();
				}
				Method[] methods = cls.getDeclaredMethods();
				String res = "";
				for (Method md : methods) {
//					判断方法上是否有注解
					if (md.isAnnotationPresent(RequestMapping.class)) {
						RequestMapping req = md.getAnnotation(RequestMapping.class);
//					读取类上的注解名称
						if (req.name().equals(noteName)) {
							System.out.println("注解名称:" + req.name());
							System.out.println("实际名称:" + noteName);
							System.out.println("参数:" + this.params.get("content") + "\t" + this.params.get("id"));
							System.out.println("方法名称:" + md.getName());
							res = (String) md.invoke(cont, this.params);
							System.out.println("执行结果:" + res);

						}
					}
				}
//			没有找到对应的方法
				if (res.equals("")) {
					Method md = cls.getMethod("defaultRequest", null);
//				使用注解的默认值
					RequestMapping mp = md.getAnnotation(RequestMapping.class);
					System.out.println("默认方法:"+mp.value());
					res = (String) md.invoke(cont, null);

				}
				System.out.println("返回的结果:" + res);
				this.resultMessage = res;
			} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | SecurityException
					| IllegalArgumentException | InvocationTargetException e) {
				System.out.println(e);
			}

		}
//Content-Type类型		
//		Content-Type: text/plain
//		请求返回字符串可用:Content-Type:text/html; charset=utf-8
//		请求图片jpg、jpeg图片:Content-Type:image/jpeg
//		请求gif图片:Content-Type:image/gif
//		Content-Type:application/octet-stream
		
//浏览器直接输入地址访问,请求头不一样:
//		GET /favicon.ico HTTP/1.1
//		Host: 127.0.0.1:8080
//		Connection: keep-alive
//		User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36
//		Accept: image/webp,image/apng,image/*,*/*;q=0.8
//		Sec-Fetch-Site: same-origin
//		Sec-Fetch-Mode: no-cors
//		Referer: http://127.0.0.1:8080/
//		Accept-Encoding: gzip, deflate, br
//		Accept-Language: zh-CN,zh;q=0.9
//		Cookie: __guid=96992031.2219997149635148000.1585503560511.6748; monitor_count=19
//post请求方式:
//		POST / HTTP/1.1
//		Host: 127.0.0.1:8080
//		Connection: keep-alive
//		Content-Length: 0
//		Origin: http://127.0.0.1:8848
//		User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36
//		Content-Type: application/x-www-form-urlencoded;charset=utf-8
//		Accept: */*
//		Referer: http://127.0.0.1:8848/JqueryTest/socket/location.html
//		Accept-Encoding: gzip, deflate, br
//		Accept-Language: zh-CN,zh;q=0.9
//get请求方式		
//		GET / HTTP/1.1
//		Host: 127.0.0.1:8080
//		Connection: keep-alive
//		Origin: http://127.0.0.1:8848
//		User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36
//		Accept: */*
//		Referer: http://127.0.0.1:8848/JqueryTest/socket/location.html
//		Accept-Encoding: gzip, deflate, br
//		Accept-Language: zh-CN,zh;q=0.9
//		解析请求头部信息
		public void parseHeaders(String headers) {
			System.out.println("解析的头部信息********************:"+headers);
			if(headers==null || headers.equals("")) {
				return;
			}
//			跨源请求可能没有
			int oripos1 = headers.indexOf("Origin");
			if(oripos1!=-1) {
				int oripos2 = headers.indexOf("\r\n",oripos1);
				
				this.allowOrigin = headers.substring(oripos1,oripos2);
				
				this.allowOrigin = this.allowOrigin.split(" ")[1];
				System.out.println("请求源:"+this.allowOrigin);
			}

			
			String firstLine = headers.substring(0, headers.indexOf("\r\n"));
			String[] arr = firstLine.split(" ");
//			get请求方式
			String methods = arr[0].toLowerCase();
			if (methods.equals("get")) {
				int pos = arr[1].indexOf("?");
//				包含请求参数
				if (pos != -1) {
					this.url = arr[1].substring(0, pos);
					String ps = arr[1].substring(pos + 1);
					System.out.println("get请求的字符串:" + ps);
					for (String ss : ps.split("&")) {
						String[] kv = ss.split("=");
						this.params.put(kv[0], kv[1]);
						System.out.println("get请求参数键值对:" + kv[0] + "\t" + kv[1]);
					}
//					不包含请求参数
				} else {
					this.url = arr[1];
					
				}
//			post请求方式
			} else {
				this.url = arr[1];
//				post请求方式
//				如果包含参数
				if (!headers.endsWith("\r\n")) {
					int pos = headers.lastIndexOf("\r\n");
					String ps = headers.substring(pos + 2);
					System.out.println("Post请求字符串:" + ps);
					for (String ss : ps.split("&")) {
						String[] kv = ss.split("=");
						params.put(kv[0], kv[1]);
						System.out.println("post请求参数键值对:" + kv[0] + "\t" + kv[1]);
					}
//					不包含参数
				}

			}
			Set<String> p = this.params.keySet();
			Iterator<String> it = p.iterator();
			while (it.hasNext()) {
				System.out.println("键值对象:" + it.next()+"\t"+params.get(it.next()));
			}
			System.out.println("请求的url:" + this.url);

		}
		public void responseHTML(OutputStream os) {
			StringBuilder response = new StringBuilder("");
			response.append("HTTP/1.1 200 OK\r\n");
			
			response.append("Access-Control-Allow-Origin:"+this.allowOrigin+"\r\n");
//			response.append("Access-Control-Allow-Origin:http://127.0.0.1:8848\r\n");
			response.append("Access-Control-Allow-Credentials:true\r\n");
			response.append("Access-Control-Allow-Headers:*\r\n");
			response.append("Content-Type:text/html; charset=utf-8\r\n\r\n");
			response.append("<!DOCTYPE html>\r\n" + 
					"<html>\r\n" + 
					"	<head>\r\n" + 
					"		<meta charset=\"utf-8\">\r\n" + 
					"		<title></title>\r\n" + 
					"	</head>\r\n" + 
					"	<body>\r\n" + 
					"	<h2 style=\"color:orange\">请求处理成功!</h2>\r\n"+
					"	<p>当前时间:"+LocalDateTime.now().toString()+"</p>\r\n"+
					"	</body>\r\n" + 
					"</html>");
				try {
					os.write(response.toString().getBytes());
					os.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				
			
			
		}
		public void responseStreamTest(OutputStream out) {
			try {
				
				System.out.println("#######################测试方法:"+this.url);
				
//				BufferedOutputStream bf = new BufferedOutputStream(out);
				BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(out));
				PrintWriter pw = new PrintWriter(out);
				StringBuilder response = new StringBuilder();
				response.append("HTTP/1.1 200 OK\r\n");
				response.append("Access-Control-Allow-Origin:"+this.allowOrigin+"\r\n");
//				response.append("Access-Control-Allow-Origin:http://127.0.0.1:8848\r\n");
				response.append("Access-Control-Allow-Credentials:true\r\n");
				response.append("Access-Control-Allow-Headers:*\r\n");
//				response.append("Accept-Ranges:bytes\r\n");
				response.append("Content-Type: text/html; charset=utf-8\r\n");
				response.append("Connection: keep-alive\r\n");
				response.append("Keep-Alive: timeout=30\r\n");
				response.append("Content-Length:"+this.resultMessage.getBytes("utf-8").length+"\r\n\r\n");
				response.append(this.resultMessage);
//		
				System.out.println("请求头部信息\n################################\n" + this.requestHead);
				System.out.println("################################");

				System.out.println("输出:\n" + response.toString());
				pw.write(response.toString());
				pw.flush();
				pw.close();

//				pw.flush();
			} catch (IOException | IllegalArgumentException | SecurityException e) {
				System.out.println(e);
			}
		}

//		返回的报文信息
		public void responseStream() {
			
			
			try {
				
				OutputStream out = socket.getOutputStream();
				if(this.url==null||this.url.equals("/")||this.params==null) {
					
					 responseHTML(out);
					 return;
					 
				}
//				分支方法测试
//				else if(this.url.equals("/queryid")) {
//					 responseStreamTest(out);
//					 return;
//				}
				System.out.println("$$$$$$$$$$$$$$$$$$$$$$$$$$$$:"+this.url);
				
				BufferedOutputStream bf = new BufferedOutputStream(out);
//				BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(out));
				PrintWriter pw = new PrintWriter(out);
				StringBuilder response = new StringBuilder();
				
				response.append("HTTP/1.1 200 OK\r\n");
				response.append("Access-Control-Allow-Origin:"+this.allowOrigin+"\r\n");
//				response.append("Access-Control-Allow-Origin:http://127.0.0.1:8848\r\n");
				response.append("Access-Control-Allow-Credentials:true\r\n");
				response.append("Access-Control-Allow-Headers:*\r\n");
//				response.append("Accept-Ranges:bytes\r\n");
				
				response.append("Content-Type: text/html; charset=utf-8\r\n");
				response.append("Connection: keep-alive\r\n");
				response.append("Keep-Alive: timeout=30\r\n");
				response.append("Transfer-Encoding:chunked\r\n\r\n");
				String info = this.resultMessage;
				
//				String info = URLEncoder.encode(this.resultMessage, "utf-8");
//				Content-Length表明了实体主体部分的大小,单位是字节
//				response.append("Content-Length:"+info.getBytes("utf-8").length+"\r\n\r\n");
				response.append(Integer.toHexString(info.getBytes().length)+"\r\n");
				response.append(info+"\r\n");
				response.append("0\r\n\r\n");
				
//		
				System.out.println("请求头部信息\n################################\n" + this.requestHead);
				System.out.println("################################");

				System.out.println("输出:\n" + response.toString());
				pw.write(response.toString());
				pw.flush();
				pw.close();
//				bf.write(response.toString().getBytes());
//				bf.flush();
//				bf.close();
//				pw.flush();
			} catch (IOException | IllegalArgumentException | SecurityException e) {
				System.out.println(e);
			}

		}


//		该方法不能用,会造成请求信息读取不到
		/*
		 * public void requestStream() throws IOException {
		 * 
		 * InputStream in = socket.getInputStream();
		 * 
		 * InputStreamReader read = new InputStreamReader(in); BufferedReader buff = new
		 * BufferedReader(read); StringBuilder builder = new StringBuilder(); String
		 * temp = null; while((temp=buff.readLine())!=null) {
		 * builder.append(temp+"\r\n"); } if(builder.toString().length()>0) {
		 * this.requestMessage = builder.toString(); }
		 * 
		 * }
		 */
	}

	public HttpService() {
		try {
			serverSocket = new ServerSocket(this.port);
			fixedPool = Executors.newFixedThreadPool(this.threadSize);
			System.out.println("ServerSocket正在监听端口:" + this.port);
			while (true) {
				Socket socket = serverSocket.accept();
				StreamProcess task = new StreamProcess(socket);
				Thread td = new Thread(task);
				fixedPool.execute(td);
				System.out.println("当前线程名:" + td.getName());
			}
		} catch (IOException e) {
			System.out.println(e);
		}

	}

}

总结分析:以上用的是socket模拟实现http请求的响应,后端使用了固定的线程池处理socket请求,并对http请求头进行解析,得到请求后通过反射调用方法处理,方法使用了注解,反射单独有一个分发控制的方法,根据请求通过反射读取注解对应的方法,然后执行,模仿了spring的设计模式,这个程序捣鼓了近三天做成现在这个样,相当不容易,这次最大的成就是解决了编码的问题,了解了http请求方式。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值