Tomcat源码分析之getParameter(String)与getQueryString()

2023-11-17

本文有些地方的描述对某些人来说可能比较罗嗦,如果想直接进入正题,可阅读“源码分析”节。但本文是自己一步步分析解决问题思路的记录,虽然有些地方的思考还不是很深入,主要是由于时间不是很充裕(虽然花了三天时间,但感觉还是不够),我会在后续的博文中,结合自己遇到的实际问题或在论坛中看到的别人提出的问题,一步步的带着问题深入分析tomcat源码,这种带着问题进行源码分析的方式,比较有针对性,不至于让自己迷失在源码的汪洋之中。如果大家对博客格式或其他方面有比较好的建议,欢迎指出,非常感谢。

本次源码分析的目标是:

弄清楚org.apache.catalina.conector.RequestFacade::getQueryString()以及getParameter(String)的不同之处及其各自的具体实现,达到此目标即完成任务。

引言
问题的引出是由于前些天在oschina上看到的一篇帖子 http://www.oschina.net/question/820641_104356,

问题分析
起初的分析思路也是受帖子作者的影响,心想出现这种情况是否是因为hashmap destroy encoding导致的,所以就google了一下hashmap encoding,得到一个比较相关的答案

 http://stackoverflow.com/questions/8427488/hashmap-destroys-encoding,这篇帖子中出现的情况也比较奇怪。

程序功能描述如下:
从文件A中读取一组以空格为分隔符的的字符串,然后将这些字符串一行一行的写入到另外一个文件B中。
如文件A的格式为:
Aaa  bbbbb cdefggg …..
文件B的格式为:
Aaa
Bbbbb
Cdefgggg
….

程序代码:

    final StringBuffer fileData = new StringBuffer(1000);

    final BufferedReader reader = new BufferedReader(

            new FileReader("fileIn.txt"));

    char[] buf = new char[1024];

    int numRead = 0;

    while ((numRead = reader.read(buf)) != -1)

    {

        final String readData = String.valueOf(buf, 0, numRead);

        fileData.append(readData);

        buf = new char[1024];

    }

    reader.close();

    String mergedContent = fileData.toString();

    mergedContent = mergedContent.replaceAll("\\<.*?>", " ");

    mergedContent = mergedContent.replaceAll("\\r\\n|\\r|\\n", " ");

    final BufferedWriter out = new BufferedWriter(

            new OutputStreamWriter(

                    new FileOutputStream("fileOut.txt")));

    final HashMap<String, String> wordsMap = new HashMap<String, String>();

    final String test[] = mergedContent.split(" ");

    for (final String string : test)

    {
        wordsMap.put(string, string);

    }
    for (final String string : wordsMap.values())

    {
        out.write(string + "\n");

    }
    out.close();


这种情况下,发现文件B中的内容为乱码,而如果将上述程序中的部分代码改为下面这样,则会得到期望的结果。

...

        for (final String string : test)
        {
                        out.write(string + "\n");
            //wordsMap.put(string, string);

        }

        //for (final String string : wordsMap.values())

        //{

        //  out.write(string + "\n");

        //}
        out.close();


出现这种情况的原因,我也不是很理解,原文中关于该贴的回答,我觉得和问题没有任何关系,大多数人都在讲如何解决这个问题,而没有提到出现上述情况的原因。

经过该贴和其他一些相关帖的了解,我发现引言中提出的问题貌似和hashmap的encoding没有任何关系,可能存在别的原因,于是自己写了一个简单的servlet来实践一下。

实践
首先是问题重现,我写了一个简单的servlet如下所示:

//请求的url为:http://localhost:8080/demo/1.do?addr=上海

@Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        //System.out.println(req);
        System.out.println("Request::getParameter(addr) is: "+ req.getParameter("addr"));
        String queryString = req.getQueryString();
        System.out.println("queryString is: "+queryString);
        String[]  params = queryString.split("[=]");
        
        Map<String, String> map = new HashMap<String, String>();
        
        map.put(params[0], params[1]);
        System.out.println("Map::get(addr) is: "+map.get(params[0]));
        return;
    }


在运行的时候,得到的结果是:

getParameter()得到的值是乱码,而通过getQueryString()解析后存放在map中的值是经过utf-8编码的。

对于getParameter()是乱码,这个原因比较明显,由于浏览器默认的urlencoding一般是utf-8,而tomcat中默认的URIEncoding是ISO-8859-1不是utf-8(为什么默认的编码是iso-8859-1?耐心看完本文后,就会明白),当客户端的请求到达tomcat的时候,tomcat就会用其他的编码方式去decode utf-8编码,那么自然就会出现乱码(具体的tomcat是如何处理queryString的,请继续阅读后面的源码分析节),所以解决方法是在tomcat的配置文件server.xml中加入如下配置(URIEncoding="utf-8"):

<Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443"
               URIEncoding="utf-8"
/>

通过上述配置文件的修改,我们得到的测试结果如下:

经上述分析,我们可以得出,getParameter()的值是根据tomcat中设置的URIEncoding编码进行decode后得到的值,而对于getQueryString() tomcat没有对其进行decode操作,保留了原有的urlencoding编码方式。

至此,我们基本可以推测,出现引言中的情况的原因是:

由于客户端对http get请求的url编码方式与tomcat中定义的URIEncoding不一致,导致tomcat服务器利用另外一种解码方式来解码客户端的url,这样必然会出现中文乱码现象。而放入Map中的字符串为什么没有出现乱码?原因就在于getQueryString()没有对客户端的url进行decode,因而保留了原有的客户端utf-8编码,所以在后面的使用过程中,如果利用utf-8对其解码,则不会出现中文乱码现象。

源码分析
经过上述实践,基本可以确定问题的原因,但为了进一步的加以验证,我试着分析了一下tomcat在处理getParameter()和getQueryString()的不同。

由于HttpServletRequest为一接口,故我们看不到其getParameter()和getQueryString()具体实现,所以我们首先需要确定request的具体实现类是什么,我们在刚才的servlet中加入如下代码:

System.out.pritnln(req);


 

通过上述打印结果,我们可以看到其具体实现类为org.apache.catalina.connector.RequestFacade,所以下一步我们的工作就是具体的分析这个类是如何处理的,也就是分析两个函数的处理流程,一是RequestFacade::getQueryString(),另外一个是RequestFacade::getParameter(String)。

首先要获得tomcat的源码,通常的做法是在eclipse中通过egit插件,将远程的git库clone下来,然后再导入工程。

所有的准备工作就绪后,接下来就是具体的源码分析工作了:

从org.apache.catalina.connector.RequestFacade这个类,我们可以看到,这是一个使用了fa&ccedil;ade模式的包装类,所以我们需要先了解一下fa&ccedil;ade模式的相关知识。

Facade模式介绍
facade模式的核心是为子系统的一组接口提供一个统一的界面,方便客户端使用子系统,客户端也不必关心子系统的具体实现。

facade设计模式的适用情况:

1. 原来的类提供的接口比较多,也比较复杂,而我们只需要使用其部分接口;

2. 原类提供的接口只能部分的满足我们的需要,且不希望重写一个新类来代替原类;

...

在本文中,RequestFacade是对Request的一个封装,由于Request本身提供的接口非常之多,而本系统中只需要使用其部分功能,在实际分析过程中,我们发现Request的具体工作最后delegate到底层的coyote.Request去做。

RequestFacade::getQueryString()分析
如何进行源码的阅读和分析?我一般的思路是,先分析正常的处理逻辑,对于那些日志,错误处理,变量定义等等可以先不用关注,从而达到快速了解整体架构或关键流程。

基于上述思路,我们得到其处理流程如下:

-RequestFacade::getQueryString()

         -Request::getQueryString()

                   -org.apache.coyote.Request::queryString()::toString()

通过以上分析可以看出,其处理流程比较简单,通过一步步的delegate,最后真正做工作的是coyote.Request,所以我们接下来只需要分析该类是如何处理。

相关函数源码如下:

org.apache.catalina.connector.RequestFacade::getQueryString()
@Override
    public String getQueryString() {
        if (request == null) {
            throw new IllegalStateException(
                            sm.getString("requestFacade.nullRequest"));
        }
        return request.getQueryString();
    }

org.apache.catalina.connector.Request::getQueryString()
/**

     * Return the query string associated with this request.

     */

    @Override
    public String getQueryString() {
        return coyoteRequest.queryString().toString();
    }

org.apache.coyote.Request::queryString()
public MessageBytes queryString() {
        return queryMB;
    }

coyote.Request::queryString()做的工作非常简单,仅是返回类型为MessageBytes的queryMB字段,但这个字段是何时被赋值的呢?这是一个非常有必要弄清的问题,因为极有可能会在赋值之前进行decode操作。

queryMB赋值分析
接下来探讨下queryMB是在何时被赋值的?

queryMB是org.apache.coyote.Request的一个私有成员变量,其数据类型为MessageBytes,定义如下:

private MessageBytes queryMB = MessageBytes.newInstance();

我们如何定位queryMB这个变量是在什么时候赋值的呢?在eclipse中,选中queryMB,点击鼠标右键,选择open call hierarchy,可以看到queryMB在哪些地方被调用,截图如下所示:

从上图可以看出,有三个地方调用了queryMB,分别是:    

public MessageBytes queryString() {
        return queryMB;
}

该函数是获得一个queryMB对象,既然获得了该对象,那么很有可能在获得对象后对其进行某些操作如赋值操作。

public void recycle() {
….
    queryMB.recycle();
….
}

顾名思义,queryMB.recycle()是对queryMB的重新回收利用,对该对象进行reset操作,和赋值没有任何联系。

public Request() {
        parameters.setQuery(queryMB);
        parameters.setURLDecoder(urlDecoder);
}

Request()构造函数中,对其成员变量parameters进行了赋值,和queryMB的赋值没有关系。

根据上述三种情况的分析,我们得出只有在第一种情况最有可能出现赋值操作,所以接下来将继续分析queryString()被哪些函数所调用,如下图所示:

从截图看出共有七个函数调用了queryString(),从函数名,我们可以简单的判断出,只有parseRequestLine(boolean)这个函数最有可能对其进行赋值,这个函数是解析http请求request line信息。

    /**
     * Read the request line. This function is meant to be used during the 
     * HTTP request header parsing. Do NOT attempt to read the request body 
     * using it.
     *
     * @throws IOException If an exception occurs during the underlying socket
     * read operations, or if the given buffer is not big enough to accommodate
     * the whole line.
     * @return true if data is properly fed; false if no data is available 
     * immediately and thread should be freed
     */
@Override
    public boolean parseRequestLine(boolean useAvailableData)
        throws IOException {
….
        if (questionPos >= 0) {
            request.queryString().setBytes(buf, questionPos + 1,
                                           end - questionPos - 1);
            request.requestURI().setBytes(buf, start, questionPos - start);
        } else {
            request.requestURI().setBytes(buf, start, end - start);
        }
….
}

从上述代码,我们可以看到,在解析http request line的时候,的确对queryMB进行了操作,直接从inputbuffer中获得字节信息,并对queryMB进行赋值。

request.queryString().setBytes(buf, questionPos + 1,
                                           end - questionPos - 1);

getQueryString()总结
由上层的Request一步步的delegate到底层,最后返回coyote.Request::queryMB()字段,而该字段是由底层直接解析http request line信息,并将得到的字节数组直接赋值给coyote.Request::queryMB。

(首先在connector.RequestFacade中调用getQueryString(),然后转交给connector.Request::getQueryString()处理,最后交由最底层的类coyote.Request直接调用getQueryString()返回该对象中保存的类型为MessageBytes的queryMB字段值,而queryMB是在解析http request line的时候,直接得到原始的bytes信息,然后保存在queryMB中,至此,上层调用的getQueryString()返回的是,未经上层任何处理,直接解析Http request line的字节信息。)

RequestFacade::getParameter()分析

(我们知道在web开发中,处理的比较多的是http get请求和http post请求,对于get请求我们可直接由url通过getParameter()方法获得,但对于post请求就会有requsetBody,那么tomcat又是如何处理的?请看后续博文分析)

继续上述getQueryString()的思路,我们先得到getParameter()的正常处理流程,如下:

-RequestFacade::getParameter(String)

         -Request::getParameter(String)

                   -Request::parseParameters()

                            -coyote.Request::getParameters()

                            -Parameters::setLimit(int)

                            -Parameters::setEncoding(String)

                            -Parameters::handleQueryParameters()

                                     -decodedQuery.duplicate(MessageBytes)

                                     -Parameters::processParameters(MessageBytes, String)

                                             -Parameters::processParameters(byte[],int,int,String) 
                   -coyote.Request::getParameters()::getParameter(String)

                            -Parameters::paramHashValues.get(String)

附录中,有上述每个函数的具体实现源码,有需要的同学可在此处查看 http://my.oschina.net/gschen/blog/120740。

从上述流程,我们可以看到,最终的处理函数是Parameter::processParameters(byte[],int,int),接下来将重点分析该方法。

Parameter::processParameters(byte[],int,int,String)该函数有四个参数,第一个参数类型是byte[],是handleQueryParameter()函数中,获得一份queryMB的拷贝,然后传给processParameters(MessageBytes,String),再传给processParameters(byte[],int,int,String)

 // -------------------- Processing --------------------
    /** Process the query string into parameters
     */
    public void handleQueryParameters() {
       ...
        try {
            decodedQuery.duplicate( queryMB );
        } catch (IOException e) {
            // Can't happen, as decodedQuery can't overflow
            e.printStackTrace();
        }
        processParameters( decodedQuery, queryStringEncoding );
    }


第二个和第三个参数类型都为int,分别是queryString的开始位置和queryString的长度如下:

public void processParameters( MessageBytes data, String encoding ) {
        ...
        ByteChunk bc=data.getByteChunk();
        processParameters( bc.getBytes(), bc.getOffset(),
                           bc.getLength(), getCharset(encoding));
    }


第四个参数为String类型,意思是利用何种方式进行解码,如果未定义,则使用默认的编码方式解码( 关于tomcat什么时候解析配置文件,获得connector节中的URIEncoding编码信息,并传到本函数的encoding,将在后面的博文中一步步的详细阐述 :tomcat源码分析之解析server.xml )。

大致的处理流程是,一步步的解析queryMB,然后将解析到的每一个parameter添加到一个HashMap<String, ArrayList<String>>中,最后在这个hashmap中根据name find到自己需要的value。

Parameters::handleQueryParameters()函数中先是得到queryMB的一份拷贝,这样可以避免对queryMB直接操作,破坏原始的信息,接着交由Parameters::processParameters(DecodedQuery, String)处理,最后交由Parameter::processParameters(byte,int,int)处理,该函数第一个参数是queryMB的一份拷贝,函数的基本功能是对该拷贝进行解析,得到一个个的解码后的parameter,再add到paramHashValues这样的一个HashMap<String, ArrayList<String>>中去。

  // -------------------- Parameter parsing --------------------
    // we are called from a single thread - we can do it the hard way
    // if needed
    ByteChunk tmpName=new ByteChunk(); 
    ByteChunk tmpValue=new ByteChunk();
    private final ByteChunk origName=new ByteChunk();
    private final ByteChunk origValue=new ByteChunk();
    CharChunk tmpNameC=new CharChunk(1024);
    public static final String DEFAULT_ENCODING = "ISO-8859-1";
    private static final Charset DEFAULT_CHARSET =
    Charset.forName(DEFAULT_ENCODING);
还记得前面提出的默认编码问题吗?您猜对了,就是在这儿定义了默认的default encoding

public static final String DEFAULT_ENCODING = "ISO-8859-1";
1.
基本思想是:遍历字节数组,依次得到name和value值,然后调用urlDecoder对name和value进行解码,最后调用addParameter(name,value)方法添加到Parameter::HashMap<String, ArrayList<string>>中去。

queryString参数解析算法描述

pos: 开始位置
end: 结束位置
while(pos < end)
    nameStart: 初始化为pos,参数名称开始位置
    nameEnd: 初始化为-1,参数名称结束位置,通过nameStart和nameEnd可获得参数名称
    valueStart: 初始化为-1,参数值开始位置
    valueEnd: 初始化为-1,参数值结束位置,通过valueStart和valueEnd可获得参数值
    parsingName:布尔类型,初始化为true,用来标识是否正在解析名称
    parameterComplete: 布尔类型,初始化为false,用来标识一个parameter是否解析完成
    
    do
        swtich(当前位置pos对应的字节)
            '=':
                是否正在解析参数名称,是则nameEnd = pos, parsingName = false, pos++, valueStart = pos;
                否则pos++;
            '&':
                是否正在解析参数名称,如果是,则nameEnd=pos,否则valueEnd = pos, pos++;
                parameterComplete=ture参数整体解析完成
                pos++;
            default:
                pos++;
    while(parameter未解析完成 且 pos < end)
    
    if(pos == end)
        if(nameEnd == -1)
            nameEnd = pos;
        if(valueStart > -1 && valueEnd == -1)
            valueEnd = pos;
end while
算法点评

上述算法的精髓在于四个位置indicator和两个boolean变量,在完成一次parameter解析后,通过nameStart,nameEnd获得parameter.name的值,通过valueStart, valueEnd获得parameter.value的值,而parameterComplete用来标识一次parameter解析是否完成,parsingName用来标识是否正在解析名称(为什么需要这个标识?因为有些时候,parameter.value可能为空如name=&passwd=123这种情况下)。

算法源码

        int pos = start;
        int end = start + len;

        while(pos < end) {
            int nameStart = pos;
            int nameEnd = -1;
            int valueStart = -1;
            int valueEnd = -1;

            boolean parsingName = true;
            boolean decodeName = false;
            boolean decodeValue = false;
            boolean parameterComplete = false;

            do {
                switch(bytes[pos]) {
                    case '=':
                        if (parsingName) {
                            // Name finished. Value starts from next character
                            nameEnd = pos;
                            parsingName = false;
                            valueStart = ++pos;
                        } else {
                            // Equals character in value
                            pos++;
                        }
                        break;
                    case '&':
                        if (parsingName) {
                            // Name finished. No value.
                            nameEnd = pos;
                        } else {
                            // Value finished
                            valueEnd  = pos;
                        }
                        parameterComplete = true;
                        pos++;
                        break;
                    case '%':
                    case '+':
                        // Decoding required
                        if (parsingName) {
                            decodeName = true;
                        } else {
                            decodeValue = true;
                        }
                        pos ++;
                        break;
                    default:
                        pos ++;
                        break;
                }
            } while (!parameterComplete && pos < end);

            if (pos == end) {
                if (nameEnd == -1) {
                    nameEnd = pos;
                } else if (valueStart > -1 && valueEnd == -1){
                    valueEnd = pos;
                }
            }
            ...
        }


上述代码通过一次遍历处理,得到nameStart, nameEnd, valueStart, valueEnd四个indicator,这样便可得到name, value值。在得到parameter.name和parameter.value后,接着就需要对其进行urldecode操作,decode完成之后,调用addParameter(name, value)方法将其添加到hashmap中。

            tmpName.setBytes(bytes, nameStart, nameEnd - nameStart);
            if (valueStart >= 0) {
                tmpValue.setBytes(bytes, valueStart, valueEnd - valueStart);
            } else {
                tmpValue.setBytes(bytes, 0, 0);
            }

            try {
                String name;
                String value;

                if (decodeName) {
                    urlDecode(tmpName);
                }
                tmpName.setCharset(charset);
                name = tmpName.toString();

                if (valueStart >= 0) {
                    if (decodeValue) {
                        urlDecode(tmpValue);
                    }
                    tmpValue.setCharset(charset);
                    value = tmpValue.toString();
                } else {
                    value = "";
                }

                try {
                    addParameter(name, value);
                } catch (IllegalStateException ise) { // Hitting limit stops processing further params but does
                    ...
                }
            } catch (IOException e) {
               ...
            }

            tmpName.recycle();
            tmpValue.recycle();


上述代码是对tmpName和tmpValue进行urldecode操作,然后将解码后的信息addParameter。

关于decode的一些说明:

在得到name和value后,调用UDecoder对其解码,如果tomcat的server.xml中未定义URIEncoding,则使用默认的"ISO-8859-1"对其进行解码。

Futuer work
在本次源码分析过程中,尚有一些未解决的问题,将在以后分析的过程中,逐步的解决。

问题列表:

1. tomcat是在什么时候加载server.xml配置文件的,得到URIEncoding值的;

2. digest是如何解析xml文件的;

3. 底层的coyote是如何实现的;

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

Tomcat源码分析之getParameter(String)与getQueryString() 的相关文章

随机推荐

  • Promethus(普罗米修斯)安装与配置(亲测可用)

    1 普罗米修斯概述 Prometheus 是由go语言 golang 开发 是一套开源的监控 报警 时间序列数 据库的组合 适合监控docker容器 Prometheus是最初在SoundCloud上构建的开源系统监视和警报工具包 自201
  • 字符串匹配算法0-基本概念

    字符串匹配的算法在很多领域都有重要的应用 这就不多说了 我们考虑一下算法的基本的描述 给定大小为 字母表 上的长度为n的文本t和长度为m的模式p 找出t中所有的p的出现的地方 一个长度为m的串p表示为一个数组p 0 m 1 这里m 0 当然
  • [前端系列第5弹]JQuery简明教程:轻松掌握Web页面的动态效果

    在这篇文章中 我将介绍jQuery的基本概念 语法 选择器 方法 事件和插件 以及如何使用它们来实现Web页面的动态效果 还将给一些简单而实用的例子 让你可以跟着我一步一步地编写自己的jQuery代码 一 什么是JQuery JQuery是
  • 【异步系列五】关于async/await与promise执行顺序详细解析及原理详解

    前段时间总结了几篇关于异步原理 Promise原理 Promise面试题 async await 原理的文章 链接如下 感兴趣的可以去看下 相信会有所收获 一篇文章理清JavaScript中的异步操作原理 Promise原理及执行顺序详解
  • 博客4:YOLOv5车牌识别实战教程:模型优化与部署

    摘要 本篇博客将详细介绍如何对YOLOv5车牌识别模型进行优化和部署 我们将讨论模型优化策略 如模型蒸馏 模型剪枝和量化等 此外 我们还将介绍如何将优化后的模型部署到不同平台 如Web 移动端和嵌入式设备等 车牌识别视频 正文 4 1 模型
  • 4.5 静态库链接

    4 5 静态库链接 一种语言的开发环境往往会附带语言库 language library 这些库通常是对操作系统API的包装 例如C语言标准库的函数strlen 并没有调用任何操作系统的API 但是很大一部分库函数都要调用操作系统API 例
  • 三目运算符优先级

    今天发表一个遇到的js的三元运算符优先级问题 如图 在解答这一题的时候 首先我们先理解什么是三元运算符 如名字一样是有三个操作数 语法 条件判断 结果1 结果2 如果条件成立 则返回结果1 否则返回结果2 在这里 三元运算符优先级是最低的
  • C语言实现TCP连接

    开发环境 TCP服务端 TCP UDP测试工具 开发环境 Linux 编程语言 C语言 TCP UDP测试工具工具的使用请自行百度 我们用这款软件模拟TCP服务端 效果展示 代码编写 include
  • bootstrap中container类和container-fluid类的区别

    近几天才开始系统的学习bootstrap 但马上就遇到了一个 拦路虎 container和container fluid到底什么区别 查了很多资料 看到很多人和我有同样的疑问 但是下面的回答一般都是一个是响应式一个宽度是百分百 说的好像是那
  • 【华为OD机试】斗地主之顺子(C++ Python Java)2023 B卷

    时间限制 C C 1秒 其他语言 2秒 空间限制 C C 262144K 其他语言524288K 64bit IO Format lld 语言限定 C clang11 C clang 11 Pascal fpc 3 0 2 Java jav
  • firefox 阻止此页面创建其他对话框的解决方法

    用Firefox操作弹出界面时总是遇到 firefox 阻止此页面创建其他对话框 点击确定后 控制台就会报错误 解决方法 1 在firefox里输入about config 2 在列表框里右键 gt 新建 gt 整数 3 输入选项名dom
  • Redis底层数据结构

    Redis简单介绍一下 Redis是一个开源的 使用C语言编写的 支持网络交互的 可基于内存也可持久化的Key Value数据库 有哪些数据结构 说起Redis数据结构 肯定先想到Redis的5 种基本数据结构 String 字符串 Lis
  • 日期类之运算符重载

    date h pragma once include
  • linux tcpdump抓包命令详解,tcpdump(抓包分析命令详解)

    TCPDump可以完全拦截网络上传输的数据包以提供分析 它支持对网络层 协议 主机 网络或端口的过滤 并提供逻辑语句 例如和 或不帮助您删除无用的信息 tcpdump抓包分析命令详解 tcpdump是一个用于截取网络分组 并输出分组内容的东
  • 如何成为一个牛逼的脚本小子日记之0x001-JAVA 代码审计 Top half (2023829-...

    如何成为一个牛逼的脚本小子日记之 0x001 JAVA 代码审计 Top half 2023 8 29 2023 9 1 此记录是在拥有一定的java基础下进行的 java基础类 反射 继承 filter servlet calssLoad
  • Springboot实现热部署

    所谓的热部署 比如项目的热部署 就是在应用程序在不停止的情况下 实现新的部署 而Springboot在我们每次修改完代码之后 可能只是修改下打印的信息 就得重新启动App类 这样太浪费时间 有没有一种修改完代码让程序自动重启的方法呢 答案是
  • DirectShow中的工具GraphEdit使用小结

    一 安装完Windows SDK 7 0或7 1后 在C Program Files Microsoft SDKs Windows v7 0 Bin下有32位的graphedt exe 及x64目录下有64位版本的graphedt exe
  • Python—PEP8规范

    Python PEP8规范 介绍 代码布局 模块导入顺序 空格 注释 注释块 命名风格 应避免的名字 模块名 类名 异常名 全局变量名 函数名 方法名和实例变量名 设计建议 Python思维导图 app siweidaotu com R06
  • MySql如何获取表头字段?实用技巧

    show columns from 表名
  • Tomcat源码分析之getParameter(String)与getQueryString()

    本文有些地方的描述对某些人来说可能比较罗嗦 如果想直接进入正题 可阅读 源码分析 节 但本文是自己一步步分析解决问题思路的记录 虽然有些地方的思考还不是很深入 主要是由于时间不是很充裕 虽然花了三天时间 但感觉还是不够 我会在后续的博文中