深入理解HTTP协议

2023-05-16

目标:

  • 掌握 http 原理,重点掌握 http Request & Response 格式
  • 掌握 http 中相关重点知识,如请求方法,属性,状态码等
  • 使用 java socket 编写简易版本 http server , 深刻理解原理
  • 掌握session和cookie

1、HTTP原理

理解为何要有应用层?

我们已经学过 TCP/IP , 已经知道目前数据能从客户端进程经过路径选择跨网络传送到服务器端进程[ IP+Port ],可是,仅仅把数据从A点传送到B点就完了吗?这就好比,在淘宝上买了一部手机,卖家[ 客户端 ]把手机通过顺丰[ 传送+路径选择 ] 送到买家 [ 服务器 ] 手里就完了吗?当然不是,买家还要使用这款产品,还要在使用之后,给卖家打分评论。所以,我们把数据从A端传送到B端, TCP/IP 解决的是顺丰的功能,而两端还要对数据进行加工处理或者使用,所以我们还需要一层协议,不关心通信细节,关心应用细节!
这层协议叫做应用层协议。而应用是有不同的场景的,所以应用层协议是有不同种类的,其中经典协议之一的HTTP就是其中的佼佼者。那么, Http 是解决什么应用场景呢?
早期用户,上网使用浏览器来进行上网,而用浏览器上网阅读信息,最常见的是查看各种网页【其实也是文件数据,不过是一系列的 html 文档,当然还有其他资源如图片, css , js 等】,而要把网页文件信息通过网络传送到客户端,或者把用户数据上传到服务器,就需要 Http 协议【当然,http作用不限于此】

我们用浏览器打开一个网页,基本都是基于HTTP 协议来传输的。
比如我们访问百度:

https://www.baidu.com/

访问网站使用的协议类型就是HTTPS(https是基于http 实现的,只不过在 http 基础上引入一个加密层)

http vs https

在这里 http 和 https 都是应用层协议,应用层协议很多时候都需要程序员来手动设定(自己指定协议),http 是大佬们已经定义好的现成的协议。
http 协议的优点:http 协议简单,支持的扩展能力很强,这样程序员就可以基于 http 进行自定制,节省开发成本。http 协议是基于 TCP 来实现的。

认识 url

平时我们俗称的 “网址” 其实就是说的 URL
在这里插入图片描述
注意服务器的地址一般是隐藏了端口号的,没有显示出来一般都是默认端口号,http(80)、https(443)

  • url 中对应的path(文件路径)不同的时候,获取到的页面也是不同的
  • url 中的服务器ip (域名)来确定一个服务器
  • url 中的服务器端口来确定这个主机上的哪个进程
  • url 中的path来确定这个进程中所管理的那个资源、文件
  • 最终http 请求得到的“网页” 本质上是一个文件
https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&tn=baidu&wd=%E7%A1%AE%E5%AE%9E%E5%BC%BA%E5%A4%A7%E7%9A%84&oq=%25E5%25BE%2597%25E5%25BE%2597&rsv_pq=9726ade400008505&rsv_t=bb1bRKFfe31d0v1TWsZop7f4HUKpykwIGV0E4Ya%2B0UMwaZHQkU3rn9lKF1w&rqlang=cn&rsv_enter=1&rsv_dl=tb&rsv_sug3=3&rsv_sug1=4&rsv_sug7=100&rsv_sug2=0&rsv_btype=t&inputT=3318&rsv_sug4=5016

我们查询字符串中,使用&符号把这些内容区分成若干个键值对,每个键值对的键和值之间使用 = 分割。
在这里键值对的具体含义外人是不清楚的,只有实现内部程序的程序员才清楚,当然不同的服务器上使用的查询字符串中的键值对内容也是不相同的。

urlencode和urldecode

像 / ? : 等这样的字符, 已经被url当做特殊意义理解了. 因此这些字符不能随意出现.
比如, 某个参数中需要带有这些特殊字符, 就必须先对特殊字符进行转义.转义的规则如下:将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成%XY
格式。
在这里插入图片描述
就如// 被转义了
urldecode就是urlencode的逆过程;

HTTP的方法

在这里插入图片描述

最初设计HTTP的时候是这样的,在现在很少按照这样来使用。
最常用的 HTTP 方法是GET和 POST

GET 和 POST 的区别(面试)

很多资料写的是GET 一般把数据放到 url 中,POST 一般把数据放到body 中。其实也不是很科学的,理论上完全可以把 POST 数据放在url 中,把 GET 的数据放在body 中。
1、GET 用于从服务器获取资源,POST用于给服务器提交数据(现在很少严格遵守这样的设计初衷,两者都可以用来获取资源或者来提交数据)
2、GET 传输的数据量上限较小(URL长度有限),POST传输数据的数据量较大(在20 年前这句话说法是对的,之前的 url 最长也就是1k-2k ,但是现在的url 可能会很长,长到甚至可能有几 M )
3、POST 比 GET 更加安全(POST 的安全其实是掩耳盗铃,只是把密码啥的放在body 中,这个密码只是不在 url 地址栏中,这样懂编程的人随便抓个包就可以看到)

HTTP的状态码

在这里插入图片描述
最常见的状态码, 比如 200(OK), 404(Not Found), 403(Forbidden), 302(Redirect, 重定向), 504(Bad Gateway)

HTTP常见Header

  • Content-Type: 数据类型(text/html等)
  • Content-Length: Body的长度
  • Host: 客户端告知服务器, 所请求的资源是在哪个主机的哪个端口上;
  • User-Agent: 声明用户的操作系统和浏览器版本信息;
  • referer: 当前页面是从哪个页面跳转过来的;(有些请求是没有 referer的,例如直接在浏览器地址栏里输入 url 或者点击收藏的网站)
  • location: 搭配3xx状态码使用, 告诉客户端接下来要去哪里访问;
  • Cookie: 用于在客户端存储少量信息. 通常用于实现会话(session)的功能;
cookie

HTTP 的特点是无状态(两次 HTTP 请求之间是没有任何关联的),那么从业务上建立起这样的关联联系,就需要使用到 cookie。它相当于保存在浏览器中的一个字符串,这个字符串是通过服务器返回响应中的 Set-Cookie 字段中来的,后续在访问该服务器,请求就会自己带上 Cookie字段。
以登录为例,一次登录过程中会涉及两次请求:验证用户名和密码,验证成功后,需要跳转到主页。
第一次交互:
请求:(没有Cookie 字段)
响应:通过服务器返回响应中的 Set-Cookie 字段
第二次交互:
请求:(重定向到首页)
在这里就是借助 Cookie 就可以把多个请求 关联到一起(Cookie 中的内容如果是一致,这几个请求就是有关联的,就好比识别用户的身份信息,你登录一次之后,此时在访问该网站的其他页面就不在需要重新登录,但是为了安全在设计的时候 Cookie是会隔一段时间会刷新的)

浏览器 是按照域名来区分 Cookie的,百度的 Cookie 和搜狗的 Cookie 相互独立,不冲突的。

HTTP协议格式

在这里插入图片描述

HTTP请求

在这里插入图片描述

  • 首行: [方法] + [url] + [版本]
  • 协议头(Header): 请求的属性, 冒号空格分割的键值对;每组属性之间使用\n分隔,此处的键值对可以是用户自己定义的,但大多数都是HTTP中已经有的,具有特定含义的内容;
  • 空行:遇到空行表示Header部分结束,
  • Body: 空行后面的内容都是Body. Body允许为空字符串. 如果Body存在, 则在Header中会有一个Content-Length属性来标识Body的长度;

HTTP响应

在这里插入图片描述

  • 首行: [版本号] + [状态码] + [状态码解释]
  • 协议头(Header): 请求的属性, 冒号分割的键值对;每组属性之间使用\n分隔;
  • 空行:表示Header部分结束
  • Body: 空行后面的内容都是Body. Body允许为空字符串. 如果Body存在, 则在Header中会有一个Content-Length属性来标识Body的长度; 如果服务器返回了一个html页面, 那么html页面内容就是在body中.

自己实现一个HTTP协议(V1版本)

主要做这几件事情:
1、把代码格式进行整理,让代码更加规范;
2、解析 URL 中包含的参数(键值对),能够方便的处理用户传过来的参数;
3、演示 Cookie 的工作流程~

HttpRequest:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;

// 表示一个 HTTP 请求, 并负责解析.
public class HttpRequest {
    private String method;
    // /index.html?a=10&b=20
    private String url;
    private String version;
    private Map<String, String> headers = new HashMap<>();
    private Map<String, String> parameters = new HashMap<>();

    // 请求的构造逻辑, 也使用工厂模式来构造.
    // 此处的参数, 就是从 socket 中获取到的 InputStream 对象
    // 这个过程本质上就是在 "反序列化"
    public static HttpRequest build(InputStream inputStream) throws IOException {
        HttpRequest request = new HttpRequest();
        // 此处的逻辑中, 不能把 bufferedReader 写到 try ( ) 中.
        // 一旦写进去之后意味着 bufferReader 就会被关闭, 会影响到 clientSocket 的状态.
        // 等到最后整个请求处理完了, 再统一关闭
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
        // 此处的 build 的过程就是解析请求的过程.
        // 1. 解析首行
        String firstLine = bufferedReader.readLine();
        String[] firstLineTokens = firstLine.split(" ");
        request.method = firstLineTokens[0];
        request.url = firstLineTokens[1];
        request.version = firstLineTokens[2];
        // 2. 解析 url 中的参数
        int pos = request.url.indexOf("?");
        if (pos != -1) {
            // 看看 url 中是否有 ? . 如果没有, 就说明不带参数, 也就不必解析了
            // 此处的 parameters 是希望包含整个 参数 部分的内容
            // pos 表示 ? 的下标
            // /index.html?a=10&b=20
            // parameters 的结果就相当于是 a=10&b=20
            String parameters = request.url.substring(pos + 1);
            // 切分的最终结果, key a, value 10; key b, value 20;
            parseKV(parameters, request.parameters);
        }
        // 3. 解析 header
        String line = "";
        while ((line = bufferedReader.readLine()) != null && line.length() != 0) {
            String[] headerTokens = line.split(": ");
            request.headers.put(headerTokens[0], headerTokens[1]);
        }
        // 4. 解析 body (暂时先不考虑)
        return request;
    }

    private static void parseKV(String input, Map<String, String> output) {
        // 1. 先按照 & 切分成若干组键值对
        String[] kvTokens = input.split("&");
        // 2. 针对切分结果再分别进行按照 = 切分, 就得到了键和值
        for (String kv : kvTokens) {
            String[] result = kv.split("=");
            output.put(result[0], result[1]);
        }
    }

    // 给这个类构造一些 getter 方法. (不要搞 setter).
    // 请求对象的内容应该是从网络上解析来的. 用户不应该修改.
    public String getMethod() {
        return method;
    }

    public String getUrl() {
        return url;
    }

    public String getVersion() {
        return version;
    }

    // 此处的 getter 手动写, 自动生成的版本是直接得到整个 hash 表.
    // 而我们需要的是根据 key 来获取值.
    public String getHeader(String key) {
        return headers.get(key);
    }

    public String getParameter(String key) {
        return parameters.get(key);
    }

    @Override
    public String toString() {
        return "HttpRequest{" +
                "method='" + method + '\'' +
                ", url='" + url + '\'' +
                ", version='" + version + '\'' +
                ", headers=" + headers +
                ", parameters=" + parameters +
                '}';
    }
}

HttpResponse:

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.HashMap;
import java.util.Map;

// 表示一个 HTTP 响应, 负责构造
// 进行序列化操作
public class HttpResponse {
    private String version = "HTTP/1.1";
    private int status;     // 状态码
    private String message; // 状态码的描述信息
    private Map<String, String> headers = new HashMap<>();
    private StringBuilder body = new StringBuilder(); // 方便一会进行拼接.
    // 当代码需要把响应写回给客户端的时候, 就往这个 OutputStream 中写就好了.
    private OutputStream outputStream = null;

    public static HttpResponse build(OutputStream outputStream) {
        HttpResponse response = new HttpResponse();
        response.outputStream = outputStream;
        // 除了 outputStream 之外, 其他的属性的内容, 暂时都无法确定. 要根据代码的具体业务逻辑
        // 来确定. (服务器的 "根据请求并计算响应" 阶段来进行设置的)
        return response;
    }

    public void setVersion(String version) {
        this.version = version;
    }

    public void setStatus(int status) {
        this.status = status;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public void setHeader(String key, String value) {
        headers.put(key, value);
    }

    public void writeBody(String content) {
        body.append(content);
    }

    // 以上的设置属性的操作都是在内存中倒腾.
    // 还需要一个专门的方法, 把这些属性 按照 HTTP 协议 都写到 socket 中.
    public void flush() throws IOException {
        BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
        bufferedWriter.write(version + " " + status + " " + message + "\n");
        headers.put("Content-Length", body.toString().getBytes().length + "");
        for (Map.Entry<String, String> entry : headers.entrySet()) {
            bufferedWriter.write(entry.getKey() + ": " + entry.getValue() + "\n");
        }
        bufferedWriter.write("\n");
        bufferedWriter.write(body.toString());
        bufferedWriter.flush();
    }
}

HttpServer:

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class HttpServerV2 {
    private ServerSocket serverSocket = null;

    public HttpServerV2(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
        System.out.println("服务器启动");
        ExecutorService executorService = Executors.newCachedThreadPool();
        while (true) {
            Socket clientSocket = serverSocket.accept();
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    process(clientSocket);
                }
            });
        }
    }

    public void process(Socket clientSocket) {
        try {
            // 1. 读取并解析请求
            HttpRequest request = HttpRequest.build(clientSocket.getInputStream());
            System.out.println("request: " + request);
            HttpResponse response = HttpResponse.build(clientSocket.getOutputStream());
            response.setHeader("Content-Type", "text/html");
            // 2. 根据请求计算响应
            if (request.getUrl().startsWith("/hello")) {
                response.setStatus(200);
                response.setMessage("OK");
                response.writeBody("<h1>hello</h1>");
            } else if (request.getUrl().startsWith("/calc")) {
                // 这个逻辑要根据参数的内容进行计算
                // 先获取到 a 和 b 两个参数的值
                String aStr = request.getParameter("a");
                String bStr = request.getParameter("b");
                // System.out.println("a: " + aStr + ", b: " + bStr);
                int a = Integer.parseInt(aStr);
                int b = Integer.parseInt(bStr);
                int result = a + b;
                response.setStatus(200);
                response.setMessage("OK");
                response.writeBody("<h1> result = " + result + "</h1>");
            } else if (request.getUrl().startsWith("/cookieUser")) {
                response.setStatus(200);
                response.setMessage("OK");
                // HTTP 的 header 中允许有多个 Set-Cookie 字段. 但是
                // 此处 response 中使用 HashMap 来表示 header 的. 此时相同的 key 就覆盖
                response.setHeader("Set-Cookie", "user=tz");
                response.writeBody("<h1>set cookieUser</h1>");
            } else if (request.getUrl().startsWith("/cookieTime")) {
                response.setStatus(200);
                response.setMessage("OK");
                // HTTP 的 header 中允许有多个 Set-Cookie 字段. 但是
                // 此处 response 中使用 HashMap 来表示 header 的. 此时相同的 key 就覆盖
                response.setHeader("Set-Cookie", "time=" + (System.currentTimeMillis() / 1000));
                response.writeBody("<h1>set cookieTime</h1>");
            } else {
                response.setStatus(200);
                response.setMessage("OK");
                response.writeBody("<h1>default</h1>");
            }
            // 3. 把响应写回到客户端
            response.flush();
        } catch (IOException | NullPointerException e) {
            e.printStackTrace();
        } finally {
            try {
                // 这个操作会同时关闭 getInputStream 和 getOutputStream 对象
                clientSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws IOException {
        HttpServerV2 server = new HttpServerV2(9090);
        server.start();
    }
}

Cookie工作原理

在这里插入图片描述

Cookie演示

我们启动服务器,演示一下Cookie 的效果:
服务器:
在这里插入图片描述
客户端:(第一次设置 Cookie)
在这里插入图片描述
第二次就会自己加上Cookie信息,在这里存的就是用户的信息:
在这里插入图片描述
我们在看看服务器:
在这里插入图片描述
我们还可以通过Fiddler抓包看看我们构造的响应和请求:在这里插入图片描述
再次理解Cookie:Cookie是可以有多个的也不是一成不变的,比如我们演示一个多次访问cookieTime,每次都获取到不同的时间戳作为Cookie。
第一次访问:
在这里插入图片描述
第二次访问:
在这里插入图片描述
在这里只是修改了 time 这个cookie, user 还是登录用户的信息,是不收影响的。

如何使用cookie 完成一次登录过程,我们得看看下边这个代码是如何实现,不仅仅是浏览器,服务器也要做出相应的逻辑实现。

V3版本http 服务器

主要工作:
1、支持返回一个静态的 html 文件
2、解析处理 cookie(把 cookie处理成键值对结构)
3、解析处理 body (把body 中的数据处理成键值对结构)
4、实现完整的登录功能(session 的简单实现)
在这里我说一下主要代码:

private void doPost(HttpRequest request, HttpResponse response) {
        // 2. 实现 /login 的处理
        if (request.getUrl().startsWith("/login")) {
            // 读取用户提交的用户名和密码
            String userName = request.getParameter("username");
            String password = request.getParameter("password");
//            System.out.println("userName: " + userName);
//            System.out.println("password: " + password);
            // 登陆逻辑就需要验证用户名密码是否正确.
            // 此处为了简单, 咱们把用户名和密码在代码中写死了.
            // 更科学的处理方式, 应该是从数据库中读取用户名对应的密码, 校验密码是否一致.
            if ("fwh".equals(userName) && "123".equals(password)) {
                // 登陆成功
                response.setStatus(200);
                response.setMessage("OK");
                response.setHeader("Content-Type", "text/html; charset=utf-8");
                // 原来登陆成功, 是给浏览器写了一个 cookie, cookie 中保存的是用户的用户名.
                // response.setHeader("Set-Cookie", "userName=" + userName);

                // 现有的对于登陆成功的处理. 给这次登陆的用户分配了一个 session
                // (在 hash 中新增了一个键值对), key 是随机生成的. value 就是用户的身份信息
                // 身份信息保存在服务器中, 此时也就不再有泄露的问题了
                // 给浏览器返回的 Cookie 中只需要包含 sessionId 即可
                String sessionId = UUID.randomUUID().toString();
                User user = new User();
                user.userName = "fwh";
                user.age = 20;
                user.school = "邮电";
                sessions.put(sessionId, user);
                response.setHeader("Set-Cookie", "sessionId=" + sessionId);

                response.writeBody("<html>");
                response.writeBody("<div>欢迎您! " + userName + "</div>");
                response.writeBody("</html>");
            } else {
                // 登陆失败
                response.setStatus(403);
                response.setMessage("Forbidden");
                response.setHeader("Content-Type", "text/html; charset=utf-8");
                response.writeBody("<html>");
                response.writeBody("<div>登陆失败</div>");
                response.writeBody("</html>");
            }
        }
    }

session 工作原理

在这里插入图片描述

response.setHeader("Set-Cookie", "userName=" + userName);

如果我们用这行代码,借助 Cookie来实现登录保持功能,这样做是不太好的,用户信息在 Cookie中,每次数据传输都要把这个 Cookie 再次发给服务器,这就意味着 Cookie 中的信息很容易泄露,甚至可以伪造,绕开登录~~所以我们就需要session了!

在服务器登录成功时,把用户信息保存在一个 hash 表中(value),同时生成一个 key(这个key 是一个唯一的字符串),sessionId,最终把 sessionId 写会到 cookie 中就好了。后续访问页面的时候,Cookie 中的内容就是 sessionId,sessionId是一个没有规律的字符串,提高了安全性。服务器可以通过 sessionId 进一步找到用户的相关信息。

String sessionId = UUID.randomUUID().toString();

这行代码就会生成一个随机的字符串,每次1调用都会生成不同的。
刚开始是没有的,响应通过 set-Cookie 来保存:
在这里插入图片描述
接下来访问就可以获得:
在这里插入图片描述
通常session 是要搭配一个 过期机制,来记录线程何时创建,何时过期,如果过期也需要你重新登录,不同的网站它自己设定的过期时间也是不同的。

Cookie 和 session 的关系

  1. 都是为了实现客户端与服务端交互而产出
  2. Cookie是保存在客户端,缺点易伪造、不安全
  3. Session是保存在服务端,会消耗服务器资源
  4. Session实现有两种方式:Cookie和URL重写
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

深入理解HTTP协议 的相关文章

  • 处理原始 HTTP 请求内容

    我正在 ASP NET 中做一个电子商务解决方案 它使用PayPal 网站支付标准 https www paypal com IntegrationCenter ic standard home html服务 除此之外 我还使用他们提供的服
  • 您可以在分块的 http 响应预告片中设置 Location 标头吗?

    HTTP 1 1 响应可以分块 spec https www rfc editor org rfc rfc2616 section 3 6 1 在最后一个块的末尾 服务器可以发送一个 预告片 其中包含附加标头 问题是 你能包括一个Locat
  • Node.js 中的 HTTPS 代理服务器

    我正在开发一个node js代理服务器应用程序 我希望它支持HTTP and HTTPS SSL 协议 作为服务器 我目前正在使用node http proxy https github com nodejitsu node http pr
  • HTTP 查询字符串和 []

    PHP 使用 在查询参数名称中 以确保多次出现的参数都出现在 GET超全局变量 否则只出现最后一次出现的情况 还有其他软件可以做到这一点吗 但从RFC 3986 https www rfc editor org rfc rfc3986 以及
  • gRPC(HTTP/2) 比使用 HTTP/2 的 REST 更快吗?

    目标是引入一种性能更好的传输和应用层协议latency and 网络吞吐量 目前 该应用程序使用REST with HTTP 1 1并且我们遇到了很高的延迟 我需要解决这个延迟问题并且我愿意使用gRPC HTTP 2 or 休息 HTTP2
  • AWS ALB 截断 HTTP 响应

    我有一个带有目标组的 ALB 和运行 PHP API 的 ECS 集群 我正在尝试查询 API 以获得 CSV 响应 但如果请求通过 ALB 我会得到被截断的结果 当我通过 SSH 连接到运行集群的 EC2 实例并尝试手动运行curl 通过
  • 是否可以在ajax get请求中获取页面的一部分?

    我知道我们可以在向服务器发出 GET 请求时获取整个页面 但是如果我只对该页面上的一个特定 div 感兴趣 或者更准确地说对其内容感兴趣 该怎么办 这里唯一的选择是获取整个页面 例如使用 jquery find 从中获取 div 内容吗 或
  • 减少1000张图片的HTTP请求?

    我知道这个问题可能听起来有点疯狂 但我想也许有人会想出一个聪明的主意 假设您在一个 HTML 页面上有 1000 个缩略图 图像大小约为5 10 kb 有没有办法在单个请求中加载所有图像 以某种方式将所有图像压缩到一个文件中 或者您对该主题
  • 删除 servlet 中的 cookie 时出现问题

    我尝试使用以下代码删除 servlet 中的 cookie Cookie minIdCookie null for Cookie c req getCookies if c getName equals iPlanetDirectoryPr
  • 如何测试“If-Modified-Since”HTTP 标头支持

    使用 PHP 如何准确测试远程网站supports If Modified Since HTTP 标头 据我所知 如果您获取的远程文件自标头请求中指定的日期以来已被修改 它应该返回 200 OK 状态 如果尚未修改 则应返回 304 Not
  • 服务器返回网页 404,但页面在浏览器中显示正常 - 为什么?

    一个奇怪的网页横亘在我面前 作为一名开发人员 我必须解开这个谜团 在任何浏览器中访问网页时 一切似乎都很正常 网页按预期显示 但是当查看控制台时 服务器实际上返回了 404 状态代码 那么浏览器为什么要渲染页面呢 查看正文显示返回了有效的
  • 除了 GET 和 POST 之外,如何从浏览器向 RESTful 应用程序发送任何内容?

    我没有得到 RESTful 的东西 是的 我知道如何从浏览器向我的应用程序发送 GET 请求 这是通过 URL 链接 a href user someone 并且还可以通过form方法发送POST请求 a
  • 在 Go 中跟踪 HTTP 请求时指定超时

    我知道通过执行以下操作来指定 HTTP 请求超时的常用方法 httpClient http Client Timeout time Duration 5 time Second 但是 我似乎不知道在跟踪 HTTP 请求时如何执行相同的操作
  • MPMoviePlayerController 播放 YouTube 视频

    如何在 iPhone 上的 MPMoviePlayerController 中播放 YouTube 视频 同时避免进入全屏模式 这个问题已经在这里提出 MPMoviePlayerController 正在播放 YouTube 视频吗 htt
  • 如何解析 Content-Disposition 标头以检索文件名属性?

    使用 go 如何解析从 http HEAD 请求检索到的 Content Disposition 标头以获取文件的文件名 此外 如何从 http HEAD 响应中检索标头本身 这样的事情正确吗 resp err http Head http
  • 使用特定 HTTP 方法链接到页面 (DELETE)

    如何像 Rails 那样链接到页面并让浏览器使用 DELETE 方法调用它 我试过 a href DELETE ME a 但不起作用 我使用 Node js 所以我可以用它来处理 DELETE 方法 你不能 链接只会触发 GET 请求 您可
  • .net core 2.0代理请求总是导致http 407(需要代理身份验证)

    我正在尝试通过 net core 2 0 Web 应用程序中的 WebProxy 发出 HTTP 请求 我得到的代码在 net框架中运行良好 所以我知道 相信 这不是环境问题 我也尝试使用两者来发出请求HttpWebRequest and
  • iOS 上的多个 HTTP 请求与单个 TCP 连接

    我正在开发一个 iPhone 应用程序 它使用我控制的基于 Web 的 API 连接到持续打开的 TCP 端口并通过 TCP API 发出请求 或者为我想要获取的所有数据发出新的 HTTP 请求 会更快或更高效吗 我认为差异可以忽略不计 但
  • 在读取正文之前拒绝 HTTP 请求

    我正在开发一个网站 用户需要上传一些非常大的文件 该网站是用 PHP 编写的 在某些情况下 我想根据标头拒绝文件 理想情况下 我想在收到标头后立即拒绝请求 而不读取正文 如果标头足以表明该文件应被拒绝 则没有理由读取 200M 的文件 此外
  • RestSharp RestClient的默认超时值是多少?

    任何人都知道默认超时值休息锐利 https github com restsharp 休息客户端 RestSharp 在底层使用 HttpWebRequest 它有一个默认超时 https msdn microsoft com en us

随机推荐

  • Go_常量、iota(枚举)的使用

    常量 常量是在程序运行过程中 xff0c 其值不可以发生改变的数据 xff0c 常量无法被获取地址 常量中的数据类型能是布尔型 数字型 xff08 整型 浮点型和复数型 xff09 字符串 常量的定义是通过const关键字完成的 xff0c
  • Go_反射的使用

    反射可以在运行时动态地检查变量和类型 xff0c 并且可以调用变量和类型的方法 获取和修改变量的值和类型等 使用反射可以使程序更加灵活 xff0c 但也需要谨慎使用 基于反射的代码是极其脆弱的 xff0c 反射中的类型错误会在真正运行的时候
  • 登录注册页怎么做

    app常见页面 xff1a 引导页 xff1a 概念 xff1a 第一次安装App或者更新App后第一次打开App时所展示的可以滑动的页面 作用 xff1a 主要是用于展示产品本身的一些核心功能点或者特色 启动页 xff1a 概念 xff1
  • win10安装appx工具_如何在Windows 10上安装.Appx或.AppxBundle软件

    win10安装appx工具 Microsoft s new Universal Windows Platform applications use the Appx or AppxBundle file format They re nor
  • 本地mysql数据库开启远程访问

    本地mysql数据库开启远程访问 1 开启远程访问端口 3306端口 依次点击控制面板 系统和安全 windows防火墙 高级设置 入站规则 xff1b 设置端口为3306 一直点下一步 xff1b PS xff1a 入站 xff1a 别人
  • Go_String常用操作

    字符串操作 xff1a len xff1a 统计字符串的长度 span class token keyword func span span class token function main span span class token p
  • Arrarys常用的方法

    int newArrary 61 Arrays copyOf int original int newarrary length 拷贝数组 xff0c 可定义要拷贝的长度 Array copyOf 有进行复制的功能 xff0c 而且底层是调
  • Unity2019 打包Android报错 Android NDK not found

    打包报错 xff1a UnityException Android NDK not found Android NDK not found or invalid NDK配置报错 xff1a Edit gt Preferences gt Ex
  • 安装zabbix proxy

    Centos7搭建zabbix proxy5 0 概述安装创建数据库导入数据下载包安装 编译过程中的报错 概述 Zabbix 代理可以代表 Zabbix 服务器收集性能和可用性数据 这样 xff0c 代理可以自己承担一些收集数据的负载并卸载
  • Mac localhost无法访问

    Mac localhost无法访问 localhost 8080和127 0 0 1 8080可以访问nginx的文件 xff0c 但输入localhost和127 0 0 1都打不开了
  • 生产者与消费者问题(线程同步经典案例)

    生产者 xff08 producer xff09 和消费者 xff08 consumer xff09 问题是并发处理中最常见的一类问题 xff0c 是一个多线程同步问题的经典案例 可以这样描述这个问题 xff0c 有一个或者多个生产者产生某
  • Git忽略提交规则 & .gitignore配置总结

    Git忽略提交规则 xff06 gitignore配置总结 在使用Git的过程中 xff0c 我们喜欢有的文件比如日志 xff0c 临时文件 xff0c 编译的中间文件等不要提交到代码仓库 xff0c 这时就要设置相应的忽略规则 xff0c
  • Spring之配置文件

    Spring简介 Spring是什么 Spring 自带 IoC xff08 Inverse of Control 控制反转 xff09 和 AOP Aspect Oriented Programming 面向切面编程 可以很方便地对数据库
  • Ubuntu开启SSH服务远程登录

    Ubuntu开启SSH服务远程登录 Ubuntu下开启ssh服务并能通过MobaXterm或者 Xshell进行远程登录 本人使用的是window10系统安装的MobaXterm window10系统安装MobaXterm可以参考 http
  • MongoDB

    一 MongoDB简介 1 集成简介 spring data mongodb提供了MongoTemplate与MongoRepository两种方式访问mongodb xff0c MongoRepository操作简单 xff0c Mong
  • 更改桌面壁纸_使用DeskSlide轻松更改桌面墙纸

    更改桌面壁纸 Looking to add some variety to your desktop instead of looking at the same wallpaper day in and day out Have fun
  • 科学素养题(2022年2月-2022年10月)

    二月科学素养 在我国山东省和山西省中间的 山 34 是 C A泰山 B吕梁山 C太行山 D沂蒙山 在一些寻宝游戏中 每个线索都会指向下一个线索的位置 玩家可以顺着这些线索一个一个找到所有的元素 这样的寻宝游戏的设计与 数据结构有着异曲同工之
  • Servlet综合练习:个人博客系统

    功能简介 1 注册新用户 2 xff09 登录已有用户 3 xff09 展示博客列表 xff08 包含文章标题以及作者信息 xff09 xff0c 点击标题就会跳转到文章详情页 4 xff09 文章详情页中 xff0c 显示文章标题 xff
  • Linux 环境搭建(如何获得一个免费云服务器)以及Linux基本指令

    搭建 Linux 环境 Linux 环境的搭建方式 主要有三种 直接安装在物理机上 但是由于 Linux 桌面使用起来非常不友好 不推荐 使用虚拟机软件 将 Linux 搭建在虚拟机上 但是由于当前的虚拟机软件 如 VMWare 之类的 存
  • 深入理解HTTP协议

    目标 xff1a 掌握 http 原理 xff0c 重点掌握 http Request amp Response 格式掌握 http 中相关重点知识 xff0c 如请求方法 xff0c 属性 xff0c 状态码等使用 java socket