Python内置库——http.client源码刨析

2023-05-16

看过了http.client的文档,趁热打铁,今天继续研究一下http.client的源码。

(一)

你会怎么实现

开始之前先让我们回忆一下一个HTTP调用的完整流程:

看到这张图,不妨先来思考一下如果要你来实现http.client,你会怎样做?

(二)

http.client是怎么设计的

现在,复习一下上篇文章关于http.client里面官方给出的一个示例:

>>> import http.client
>>> conn = http.client.HTTPSConnection("www.python.org")
>>> conn.request("GET", "/")
>>> r1 = conn.getresponse()
>>> print(r1.status, r1.reason)
200 OK
>>> data1 = r1.read()  # This will return entire content.

单单从这个示例,我们可以看出,http.client提供了HTTPSConnection类,首先需要实例化该类,然后调用request()方法发送请求,最后调用getresponse()方法来获得响应。

奇怪的事情发生了,在没有打开http.client的源代码之前,我们已经开始感叹HTTP协议是如此的简单。

然而HTTP协议真的易于实现吗?

(三)

http.client状态机

如果有小伙伴之前打开过client.py文件,首先映入眼帘就是一个关于状态说明文档,这里我把纯文本的文档制作成了一个状态机,如下图:

有了这个状态机,对于http.client的源码阅读会事半功倍。

(四)

源码预热



# HTTPMessage, parse_headers(), and the HTTP status code constants are
# intentionally omitted for simplicity
__all__ = ["HTTPResponse", "HTTPConnection",
           "HTTPException", "NotConnected", "UnknownProtocol",
           "UnknownTransferEncoding", "UnimplementedFileMode",
           "IncompleteRead", "InvalidURL", "ImproperConnectionState",
           "CannotSendRequest", "CannotSendHeader", "ResponseNotReady",
           "BadStatusLine", "LineTooLong", "RemoteDisconnected", "error",
           "responses"]

首先,这里指明了http.client对外提供的API,可以看到除了HTTPResponse和HTTPConnection之外,剩下大多是自定义的错误消息。

HTTP_PORT = 80
HTTPS_PORT = 443

紧接着定义了HTTP和HTTPS的默认端口。

_UNKNOWN = 'UNKNOWN'


# connection states
_CS_IDLE = 'Idle'
_CS_REQ_STARTED = 'Request-started'
_CS_REQ_SENT = 'Request-sent'

随后,又定义了一些内部状态。

咦,似乎比状态机里面的可能的状态要少呢?

(四)

HTTPResponse

先来看HTTPResponse的实例化方法:

class HTTPResponse(io.BufferedIOBase):
    def __init__(self, sock, debuglevel=0, method=None, url=None):
        self.fp = sock.makefile("rb")
        self.debuglevel = debuglevel
        self._method = method


        self.headers = self.msg = None


        # from the Status-Line of the response
        self.version = _UNKNOWN # HTTP-Version
        self.status = _UNKNOWN  # Status-Code
        self.reason = _UNKNOWN  # Reason-Phrase


        self.chunked = _UNKNOWN         # is "chunked" being used?
        self.chunk_left = _UNKNOWN      # bytes left to read in current chunk
        self.length = _UNKNOWN          # number of bytes left in response
        self.will_close = _UNKNOWN      # conn will close at end of response

在这里,初始化了一些状态,通过makefile将入参sock当作了一个可读的文件对象,但是HTTPResponse本身又是继承至io.BufferedIOBase,所以HTTPResponse本身也提供了read方法。

class HTTPResponse(io.BufferedIOBase):
    def read(self, amt=None):
        if self.fp is None:
            return b""


        if self._method == "HEAD":
            self._close_conn()
            return b""


        if amt is not None:
            # Amount is given, implement using readinto
            b = bytearray(amt)
            n = self.readinto(b)
            return memoryview(b)[:n].tobytes()
        else:
            # Amount is not given (unbounded read) so we must check self.length
            # and self.chunked


            if self.chunked:
                return self._readall_chunked()


            if self.length is None:
                s = self.fp.read()
            else:
                try:
                    s = self._safe_read(self.length)
                except IncompleteRead:
                    self._close_conn()
                    raise
                self.length = 0
            self._close_conn()        # we read everything
            return s


咦?好像read方法直接返回self.fp.read()即可,为什么还会这么复杂呢?

可以看到read方法除了开头的异常判断之外,增加了对于HEAD请求的特殊处理,另外剩下的大多数代码都是因为分块传输的存在而额外增加的。

这里插入一下分块传输的方案图:

没想到,看起来简单的HTTP分块传输,却要额外增加这么些代码。

回到read方法,好像调用read返回的只有响应体,响应行和响应头去哪了?

原来除了read方法之外,HTTPResponse还提供了一个begin方法用来接收响应行和响应头。

class HTTPResponse(io.BufferedIOBase):
    def begin(self):
        if self.headers is not None:
            # we've already started reading the response
            return


        # read until we get a non-100 response
        while True:
            version, status, reason = self._read_status()
            if status != CONTINUE:
                break
            # skip the header from the 100 response
            while True:
                skip = self.fp.readline(_MAXLINE + 1)
                if len(skip) > _MAXLINE:
                    raise LineTooLong("header line")
                skip = skip.strip()
                if not skip:
                    break
                if self.debuglevel > 0:
                    print("header:", skip)


        self.code = self.status = status
        self.reason = reason.strip()
        if version in ("HTTP/1.0", "HTTP/0.9"):
            # Some servers might still return "0.9", treat it as 1.0 anyway
            self.version = 10
        elif version.startswith("HTTP/1."):
            self.version = 11   # use HTTP/1.1 code for HTTP/1.x where x>=1
        else:
            raise UnknownProtocol(version)


        self.headers = self.msg = parse_headers(self.fp)


        if self.debuglevel > 0:
            for hdr, val in self.headers.items():
                print("header:", hdr + ":", val)


        # are we using the chunked-style of transfer encoding?
        tr_enc = self.headers.get("transfer-encoding")
        if tr_enc and tr_enc.lower() == "chunked":
            self.chunked = True
            self.chunk_left = None
        else:
            self.chunked = False


        # will the connection close at the end of the response?
        self.will_close = self._check_close()


        # do we have a Content-Length?
        # NOTE: RFC 2616, S4.4, #3 says we ignore this if tr_enc is "chunked"
        self.length = None
        length = self.headers.get("content-length")


         # are we using the chunked-style of transfer encoding?
        tr_enc = self.headers.get("transfer-encoding")
        if length and not self.chunked:
            try:
                self.length = int(length)
            except ValueError:
                self.length = None
            else:
                if self.length < 0:  # ignore nonsensical negative lengths
                    self.length = None
        else:
            self.length = None


        # does the body have a fixed length? (of zero)
        if (status == NO_CONTENT or status == NOT_MODIFIED or
            100 <= status < 200 or      # 1xx codes
            self._method == "HEAD"):
            self.length = 0


        # if the connection remains open, and we aren't using chunked, and
        # a content-length was not provided, then assume that the connection
        # WILL close.
        if (not self.will_close and
            not self.chunked and
            self.length is None):
            self.will_close = True

看起来begin方法比read还要复杂的多,这主要还是因为HTTP的发展早已超出了其设计初衷。早在HTTP0.9版本,甚至都没有HTTP头部的设计,在随后的演进中,出现了HTTP头部,但是却要求它即负责表达HTTP实体的内容特征,如Content-Length等;又要求它负责控制HTTP连接的行为,如Connection。

从第25-31行,可以看出http.client目前最高仅支持HTTP1.1,已经出现的HTTP2.0乃至HTTP3.0均不支持。

随后,第33行,响应头部分被保存在了self.headers属性中,第33行之后的一大串逻辑验证了我们关于HTTP头部其混乱性的观点。

(五)

HTTPConnection

class HTTPConnection:
    _http_vsn = 11
    _http_vsn_str = 'HTTP/1.1'


    response_class = HTTPResponse
    default_port = 80
    auto_open = 1
    debuglevel = 0

可以看到http.client默认使用HTTP1.1版本,默认使用HTTPResponse接收响应,默认使用80端口。

注:HTTPSConnection 继承至HTTPConnection,区别在于连接时多了SSL

鉴于HTTPConnection的内部方法较多,咱们依照前面的状态机里面提到的顺序,依次来看一下HTTPConnection对外提供的API。

首先是实例化方法:

class HTTPConnection:
    def __init__(self, host, port=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
                 source_address=None, blocksize=8192)
        ...

其实例化方法要求host必填,其他入参均是带有缺省值。

紧接着是putrequest:

class HTTPConnection:
    def putrequest(self, method, url, skip_host=False,
                   skip_accept_encoding=False):
       """Send a request to the server"""


        # if a prior response has been completed, then forget about it.
        if self.__response and self.__response.isclosed():
            self.__response = None




        # in certain cases, we cannot issue another request on this connection.
        # this occurs when:
        #   1) we are in the process of sending a request.   (_CS_REQ_STARTED)
        #   2) a response to a previous request has signalled that it is going
        #      to close the connection upon completion.
        #   3) the headers for the previous response have not been read, thus
        #      we cannot determine whether point (2) is true.   (_CS_REQ_SENT)
        #
        # if there is no prior response, then we can request at will.
        #
        # if point (2) is true, then we will have passed the socket to the
        # response (effectively meaning, "there is no prior response"), and
        # will open a new one when a new request is made.
        #
        # Note: if a prior response exists, then we *can* start a new request.
        #       We are not allowed to begin fetching the response to this new
        #       request, however, until that prior response is complete.
        #
        if self.__state == _CS_IDLE:
            self.__state = _CS_REQ_STARTED
        else:
            raise CannotSendRequest(self.__state)


        # Save the method for use later in the response phase
        self._method = method


        url = url or '/'
        self._validate_path(url)


        request = '%s %s %s' % (method, url, self._http_vsn_str)


        self._output(self._encode_request(request))


        if self._http_vsn == 11:
            # Issue some standard headers for better HTTP/1.1 compliance


            if not skip_host:
                # this header is issued *only* for HTTP/1.1
                # connections. more specifically, this means it is
                # only issued when the client uses the new
                # HTTPConnection() class. backwards-compat clients
                # will be using HTTP/1.0 and those clients may be
                # issuing this header themselves. we should NOT issue
                # it twice; some web servers (such as Apache) barf
                # when they see two Host: headers


                # If we need a non-standard port,include it in the
                # header.  If the request is going through a proxy,
                # but the host of the actual URL, not the host of the
                # proxy.


                netloc = ''
                if url.startswith('http'):
                    nil, netloc, nil, nil, nil = urlsplit(url)


                if netloc:
                    try:
                        netloc_enc = netloc.encode("ascii")
                    except UnicodeEncodeError:
                        netloc_enc = netloc.encode("idna")
                    self.putheader('Host', netloc_enc)
                else:
                    if self._tunnel_host:
                        host = self._tunnel_host
                        port = self._tunnel_port
                    else:
                        host = self.host
                        port = self.port


                    try:
                        host_enc = host.encode("ascii")
                    except UnicodeEncodeError:
                        host_enc = host.encode("idna")


                    # As per RFC 273, IPv6 address should be wrapped with []
                    # when used as Host header


                    if host.find(':') >= 0:
                        host_enc = b'[' + host_enc + b']'


                    if port == self.default_port:
                        self.putheader('Host', host_enc)
                    else:
                        host_enc = host_enc.decode("ascii")
                        self.putheader('Host', "%s:%s" % (host_enc, port))


            # note: we are assuming that clients will not attempt to set these
            #       headers since *this* library must deal with the
            #       consequences. this also means that when the supporting
            #       libraries are updated to recognize other forms, then this
            #       code should be changed (removed or updated).


            # we only want a Content-Encoding of "identity" since we don't
            # support encodings such as x-gzip or x-deflate.
            if not skip_accept_encoding:
                self.putheader('Accept-Encoding', 'identity')


            # we can accept "chunked" Transfer-Encodings, but no others
            # NOTE: no TE header implies *only* "chunked"
            #self.putheader('TE', 'chunked')


            # if TE is supplied in the header, then it must appear in a
            # Connection header.
            #self.putheader('Connection', 'TE')


        else:
            # For HTTP/1.0, the server will assume "not chunked"
            pass

看到第29行至32行,有经验的小伙伴已经能够意识到,HTTPConnection其实是只能单线程运行的,如果非要在多线程里运行,就需要上层调用者控制不能多个线程同时调用同一个HTTPConnection实例。

第37行至42行,格式化了请求行。但是随后大量的注释和代码想我们形象的展示了为了兼容各版本的HTTP协议,具体的代码实现有多复杂。

随后是putheader:

class HTTPConnection:
    def putheader(self, header, *values):
        """Send a request header line to the server.


        For example: h.putheader('Accept', 'text/html')
        """
        if self.__state != _CS_REQ_STARTED:
            raise CannotSendHeader()


        if hasattr(header, 'encode'):
            header = header.encode('ascii')


        if not _is_legal_header_name(header):
            raise ValueError('Invalid header name %r' % (header,))


        values = list(values)
        for i, one_value in enumerate(values):
            if hasattr(one_value, 'encode'):
                values[i] = one_value.encode('latin-1')
            elif isinstance(one_value, int):
                values[i] = str(one_value).encode('ascii')


            if _is_illegal_header_value(values[i]):
                raise ValueError('Invalid header value %r' % (values[i],))


        value = b'\r\n\t'.join(values)
        header = header + b': ' + value
        self._output(header)

通过代码来看,这一步其实相对比较简单,分别对header和value做了校验,值得注意的是HTTP协议即允许一个header有多个值,也允许一条请求有多个同名的header。

再来看endheaders:

class HTTPConnection:
    def endheaders(self, message_body=None, *, encode_chunked=False):
        """Indicate that the last header line has been sent to the server.


        This method sends the request to the server.  The optional message_body
        argument can be used to pass a message body associated with the
        request.
        """
        if self.__state == _CS_REQ_STARTED:
            self.__state = _CS_REQ_SENT
        else:
            raise CannotSendHeader()
        self._send_output(message_body, encode_chunked=encode_chunked)

endheaders方法更简单,先是更新了内部状态,随后调用self._send_output真正的将请求发出。

请求既然发出,下一步就是通过getresponse获取响应:

class HTTPConnection:
    def getresponse(self):
        """Get the response from the server.


        If the HTTPConnection is in the correct state, returns an
        instance of HTTPResponse or of whatever object is returned by
        the response_class variable.


        If a request has not been sent or if a previous response has
        not be handled, ResponseNotReady is raised.  If the HTTP
        response indicates that the connection should be closed, then
        it will be closed before the response is returned.  When the
        connection is closed, the underlying socket is closed.
        """


        # if a prior response has been completed, then forget about it.
        if self.__response and self.__response.isclosed():
            self.__response = None


        # if a prior response exists, then it must be completed (otherwise, we
        # cannot read this response's header to determine the connection-close
        # behavior)
        #
        # note: if a prior response existed, but was connection-close, then the
        # socket and response were made independent of this HTTPConnection
        # object since a new request requires that we open a whole new
        # connection
        #
        # this means the prior response had one of two states:
        #   1) will_close: this connection was reset and the prior socket and
        #                  response operate independently
        #   2) persistent: the response was retained and we await its
        #                  isclosed() status to become true.
        #
        if self.__state != _CS_REQ_SENT or self.__response:
            raise ResponseNotReady(self.__state)


        if self.debuglevel > 0:
            response = self.response_class(self.sock, self.debuglevel,
                                           method=self._method)
        else:
            response = self.response_class(self.sock, method=self._method)


        try:
            try:
                response.begin()
            except ConnectionError:
                self.close()
                raise
            assert response.will_close != _UNKNOWN
            self.__state = _CS_IDLE


            if response.will_close:
                # this effectively passes the connection to the response
                self.close()
            else:
                # remember this, so we can tell when it is complete
                self.__response = response


            return response
        except:
            response.close()
            raise

可以看到在真正的返回response对象之前,getresponse内部调用了response实例的begin()方法,将响应头先一步读取完毕,留下未读取的响应体由上层调用方决定。

最后需要单独介绍一下request方法:

class HTTPConnection:
    def request(self, method, url, body=None, headers={}, *,
                encode_chunked=False):
        """Send a complete request to the server."""
        self._send_request(method, url, body, headers, encode_chunked)

request方法相当于putrequest + ( putheader() *) +  endheaders(),通过调用该方法免去了繁琐的调用之苦。

(六)

总结

一路看下来,整个http.client文件共计约1500行,好多注释都是为了说明历史背景,好多代码都是为了兼容各版本,还有一些是因为HTTP头部功能的多样性而引入的必要控制逻辑。

所以,看起来简洁的HTTP协议其实内部隐藏大量的复杂实现。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Python内置库——http.client源码刨析 的相关文章

  • 回溯法——素数环C++实现

    本文共928个字 xff0c 预计阅读时间需要3分钟 回溯法简介 回溯法按深度优先策略搜索问题的解空间树 首先从根节点出发搜索解空间树 xff0c 当算法搜索至解空间树的某一节点时 xff0c 先利用剪枝函数判断该节点是否可行 xff08
  • Prime Path素数筛与BFS动态规划

    本文共2053个字 xff0c 预计阅读时间需要6分钟 BFS BFS xff0c 其英文全称是Breadth First Search BFS并不使用经验法则算法 从算法的观点 xff0c 所有因为展开节点而得到的子节点都会被加进一个先进
  • C++读取和写入文件(fstream等)

    本文共321个字 xff0c 预计阅读时间需要1分钟 2019年7月非常忙 xff0c 这大概是这个月的第一篇吧 某高校机试需要从文件中读取数据并将数据写入到文件中 完成这一操作需要用到fstream模块 xff0c 网上一堆资料 xff0
  • 并查集应用——PAT甲级2019春季

    并查集适用问题举例 1 已知 xff0c 有n个人和m对好友关系 2 如果两个人是直接的或者间接的好友 xff08 好友的好友的好友 xff09 xff0c 那么他们属于一个集合 xff0c 就是一个朋友圈中 3 写出程序 xff0c 求这
  • 抽象工厂模式与单件模式C++混合实现

    抽象工厂 当每个抽象产品都有多于一个的具体子类的时候 xff0c 工厂角色怎么知道实例化哪一个子类呢 xff1f 比如每个抽象产品角色都有两个具体产品 抽象工厂模式提供两个具体工厂角色 xff0c 分别对应于这两个具体产品角色 xff0c
  • source命令自动运行terminal的指令

    source命令也称为 点命令 xff0c 也就是一个点符号 xff08 xff09 source命令通常用于重新执行刚修改的初始化文件 xff0c 使之立即生效 xff0c 而不必注销并重新登录 用法 xff1a source filen
  • 适配器模式C++实现

    本文共916个字 xff0c 预计阅读时间需要3分钟 简介 适配器模式 xff1a 将一个类的接口转换成客户希望的另一个接口 适配器模式让那些接口不兼容的类可以一起工作 适配器模式的别名为包装器 Wrapper 模式 xff0c 它既可以作
  • 装饰模式C++实现

    简介 动态地给一个对象添加一些额外的职责 就增加功能来说 xff0c 装饰模式比生成子类更为灵活 动机 有时我们希望给某个对象而不是整个类添加一些功能 使用继承机制是添加功能的一种有效途径 xff0c 但不够灵活 xff0c 用户不能控制对
  • 软件测试——测试计划

    完整版 xff08 包括表格和图片 xff09 请访问 xff1a http www omegaxyz com 2019 08 02 software testing 本文共6034个字 xff0c 预计阅读时间需要16分钟 文章目录 1简
  • 享元模式C++实现(flyweight)

    简介 动机 假设成立一个外包公司 xff0c 主要给一些私营业主建网站 商家客户都类似 xff0c 要求也就是信息发布 产品展示 博客留言 论坛等功能 各个客户要求差别不大 xff0c 但客户数量多 内部状态和外部状态 在享元对象内部并且不
  • mat格式数据集转换为arff与txt格式

    本文共239个字 xff0c 预计阅读时间需要1分钟 下面的代码给出了将mat格式数据集转换为arff与txt格式的matlab代码 注意 xff0c 每个 mat文件中只有一个数据集 xff0c 其中共有m 43 1列 xff0c 最后一
  • 给linux 增加软件图标

    文章目录 其实如果是自己用的软件 xff0c 没必要像wps 安装之后 图标的复杂性 xff0c 我这里更加简单1 复制一个稍微简单的 desktop 文件并重命名你安装的软件的名称2 改动里面的东西 xff0c 下面 是Typora 软件
  • 计算机组成原理--I/O系统

    大学峡谷秀 xff0c 机组未学溜 xff0c 如今涕泪流 xff0c 共勉之 近来学习netty零拷贝 xff0c 复习并笔记之 1 概念 以主机为中心 xff0c 将信息从外部设备读入或输出的操作称为IO xff0c 外部设备包括输入输
  • ae怎样设置gpu渲染

    1 方法 xff1a 在 编辑 首选项 常规 中 选择 预览 xff0c 旁边有个 GPU信息 光线追踪 选择GPU即可 2 对于 GPU 显示灰色 xff08 既不可选 xff09 的朋友 在AE插件目录下 xff08 Support F
  • conio.h头文件

    conio h conio h不是C标准库中的头文件 conio是Console Input Output xff08 控制台输入输出 xff09 的简写 xff0c 其中定义了通过控制台进行数据输入和数据输出的函数 xff0c 主要是一些
  • 企业微信如何关联小程序?

    我们在日常使用微信时 xff0c 经常会用到小程序功能 xff0c 直接从微信中打开第三方页面 xff0c 很是方便 xff0c 那么自从企业微信与微信打通之后 xff0c 我们该如何将小程序与企业微信联系起来使用呢 xff1f 其实 xf
  • 在 Mac OS X 上安装 TensorFlow

    在 Mac OS X 上安装 TensorFlow 原文地址 xff1a https www cnblogs com tensorflownews p 7298646 html 这个文档说明了如何在 Mac OS X 上安装 TensorF
  • linux /centos 中OpenSSL升级方法详解

    OpenSSL升级前段时间出现天大bug了 xff0c 这样导致大家都急着去升级OpenSSL来初安全了 xff0c 但是很多的朋友在家linux并不知道如何去升级OpenSSL了 xff0c 下面我整理了一文章大家一起参考一下 相关软件下
  • 【字符验证】java el正则表达式使用

    支持 xff1a 中文 英文 下划线 xff0c 单独或三者合一 xff1a String regex 61 34 u4E00 u9FA5A Za z0 9 43 34 if 34 Adsf 最大的 34 matches regex Sys
  • synchronized原理

    一 synchronized简介 1 java中的关键字 xff0c 在JVM层面上围绕着内部锁 intrinsic lock 或者监管锁 xff08 Monitor Lock xff09 的实体建立的 xff0c Java利用锁机制实现线

随机推荐

  • 11-JUC中的Condition对象

    文章目录 ConditionCondition常用方法总结参考 Condition 任何一个java对象都天然继承于Object类 xff0c 在线程间实现通信的往往会应用到Object的几个方法 xff0c 比如wait wait lon
  • 使用微软New Bing Chat GPT-4生成AI图像的技巧

    在聊天的创意模式中 xff0c 你现在可以要求Bing为你创建一个全新的图像 xff0c 只用你的话语 只需说 34 为我创建一个图像 34 或 34 为我绘制一个图像 34 xff0c 最后准确地说出你要找的东西 当你的描述性更强时 xf
  • 空间权重矩阵

    空间权重矩阵 前言一 空间权重矩阵是什么 xff1f 二 构建模型1 方法1 基于邻接关系构建2 基于距离构建3 复合型 三 总结 前言 随着学习的深入 xff0c 特别是在做空间统计分析的时候 xff0c 空间权重矩阵越来越频繁的出现在我
  • 时间序列模型——AR、MA、ARMA、ARIMA

    这里写目录标题 时间序列模型自回归模型差分与非平稳序列差分检验不平稳 移动平均模型移动平均法MA模型 ARMA模型ARIMA建模方法 时间序列模型 常用的时间序列模型有四种 xff1a 自回归模型 AR p 移动平均模型 MA q 自回归移
  • spring ioc容器中某个Class的bean对象是否只有一个,是否就是单例的[spring总结]

    package com xxx product web 64 program product 64 description 64 create 2021 05 12 17 28 public interface OrderService b
  • 记ftpClient.storeFile(name,inputStream)被挂起,一直没有回复226 transfer complete造成阻塞

    最近现场一个问题折磨了我好久 xff0c 问题是这样的 程序使用异步线程从装置上取文件 xff0c 通过http连接从设备上读取流之后保存到ftp服务器上 xff0c 开始都是正常的 xff0c 过了半个多小时线程会堵塞 xff0c 导致所
  • FtpClient.storeFile()函数总是返回false

    今天我在使用FTP上传图片到服务器时 xff0c 使用函数FtpClient storeFile filename inputFile 函数上传 我发现返回值总是false 但是明明图片已经上传成功 后来查阅资料在这之前加上一句话就好了 x
  • Mac 上实现便捷 Python 多版本共存和轻松切换

    1 安装Homebrew span class hljs operator style margin 0px padding 0px span span class hljs string style margin 0px padding
  • RHEL下修改VNC的分辨率

    在使用LINUX下的CAE设计软件时 xff0c 由于系统自带的VNC默认分辨绿为1024 768 xff0c 导致部分地区无法显示 经试验 xff0c 修改如下内容可以调整分辨率 xff1a 1 which vncserver 得到VNC
  • Docker与虚拟机的简介以及比较

    Docker与虚拟机 注意 本人的博客都迁移到本人自己搭建的博客地址 xff0c 通过此处可查看 Docker 什么是Docker Docker是一个集开发 打包 运行应用于一体的开放式平台 Docker可以用来快速交付应用 使用Docke
  • 教你如何拥有好看的CMD界面 如何美化Windows Terminal

    安装Windows Terminal 在Mircosoft Store可以安装 只有6M PowerShell 必备条件 使用 PowerShell xff0c 安装 Posh Git 和 Oh My Posh xff1a Install
  • CentOS 中 VNCServer 安装使用

    xfeff xfeff 参考 xff1a 1 http wiki centos org HowTos VNC Server 2 http www2 starcat ne jp kanocl shumi vnc htm 分割线 在centos
  • 程序员之歌:我是一个程序员

    工作一天 xff0c 来一起唱首歌缓解疲劳吧猿媛们
  • 如何准备校招技术面试+一只小菜鸟的面试之路

    校 招一路走来很幸运 xff0c 从最初的迷茫 xff0c 到偶尔的被否认 xff0c 然后慢慢调整心态 xff0c 有缺憾才能有进步 xff0c 正视的自己不足 xff0c 静下心努力提高 xff0c 勇敢尝试各种面试机会 xff0c 因
  • 9 个很酷的 CMD 命令

    开发者 xff08 KaiFaX xff09 面向全栈工程师的开发者 专注于前端 Java Python Go PHP的技术社区 大家好 xff0c 我是若飞 今天给大家推荐几个很酷的CMD命令 使用得当 xff0c 可以让你事半功倍 ip
  • MariaDB INSERT INTO SELECT 报错

    文章目录 1 错误描述2 原因3 例子参考文献 1 错误描述 MariaDB 10 5 9 在使用 INSERT INTO SELECT 时会报错 xff0c 错误如下 xff1a ERROR 1064 42000 You have an
  • 科班程序员逆袭为渗透测试工程师的坎坷路(第一篇)

    渗透测试工程师 1 什么是渗透测试 渗透测试 xff08 penetration test xff09 事实上并没有一个标准的定义 xff0c 在国外的大部分安全组织达成的统一说法是 xff1a 渗透测试是通过模拟恶意黑客的攻击方法 xff
  • XD基础操作演示

    基础操作演示 1 启动页面 xff0c 页面上展示了几种画板类型 xff0c 根据项目类型选择相应的画板 xff0c 或者自定义画板大小 在XD中是使用一倍图进行设计 xff0c 由于XD是矢量设计软件 xff0c 所以导出 64 2x 6
  • SQL中的注释语句(三种注释)

    一 单行注释 采用 34 34 双减号 进行单行注释 xff0c 注意 xff1a 34 34 与注释内容要用空格隔开才会生效 二 多行注释 采用 进行多行注释 三 xff0c xff08 单行 xff09 注释 在mysql数据库中就可使
  • Python内置库——http.client源码刨析

    看过了http client的文档 xff0c 趁热打铁 xff0c 今天继续研究一下http client的源码 xff08 一 xff09 你会怎么实现 开始之前先让我们回忆一下一个HTTP调用的完整流程 xff1a 看到这张图 xff