zoukankan      html  css  js  c++  java
  • 本地tomcat调用远程接口报错:java.lang.reflect.InvocationTargetException

      今天碰到一个奇怪的问题,本地Eclipse起了一个tomcat通过http去调一个外部接口,结果竟然报了一个反射的异常,先看下完整日志:

    四月 12, 2018 4:00:14 下午 org.apache.catalina.startup.Catalina start
    信息: Server startup in 31334 ms
    [2018-04-12 16:01:00] DEBUG EnvironmentInterceptor:33 - EnvironmentInterceptor.preHandle requesturl:http://127.0.0.1:8080/ms-search-war/ms.search.searchService/getSearchRank
    [2018-04-12 16:01:00] DEBUG EnvironmentInterceptor:34 - RequestHeads,User-Agent=Apache-HttpClient/4.1.1 (java 1.5),X-Identity-ID=15077870000,X-Auth-Token=123,X-Login-Type=2
    [2018-04-12 16:01:00] DEBUG MsCommonAspect:128 - Enter into UrlAdviser.setUrlParam request param = {"paramMap": [{"key": "pluginCode","value": "search_rank"},{"key": "title","value": ""},{"key": "rankType","value": "5"},{"key": "rankDateType","value": "2"},{"key": "pageNo","value": "1"},{"key": "pageSize","value": "10"},{"key": "isMarginTop","value": ""},{"key": "isMarginBottom","value": ""},{"key": "isPaddingTop","value": ""},{"key": "isShowLine","value": ""},{"key": "isAjax","value": ""},{"key": "nodeId","value": ""},{"key": "book_id_list","value": ""},{"key": "listen_book_id_list","value": ""},{"key": "magzine_id_list","value": ""},{"key": "kw","value": "+++++"},{"key": "itemType","value": "0"},{"key": "source","value": "1"}]},identityId = 15077870000
    [2018-04-12 16:01:00] INFO  PerformanceMonitorComponent:66 - [15ms] - interface:[ IGaiaClient ],method:[ get ]USER_SESSION_KEY:15077870000:cm
    
    [2018-04-12 16:01:00] DEBUG MsCommonAspect:169 - Exit UrlAdviser.setUrlParam success , nln= null ,cm = M801005I ,identityId = 15077870000 
    [2018-04-12 16:01:00] DEBUG GetSearchRankMethodImpl:83 - Enter GetSearchRankMethodImpl.getSearchRank ,identityId:15077870000 ComponentRequest :{"paramMap": [{"key": "pluginCode","value": "search_rank"},{"key": "title","value": ""},{"key": "rankType","value": "5"},{"key": "rankDateType","value": "2"},{"key": "pageNo","value": "1"},{"key": "pageSize","value": "10"},{"key": "isMarginTop","value": ""},{"key": "isMarginBottom","value": ""},{"key": "isPaddingTop","value": ""},{"key": "isShowLine","value": ""},{"key": "isAjax","value": ""},{"key": "nodeId","value": ""},{"key": "book_id_list","value": ""},{"key": "listen_book_id_list","value": ""},{"key": "magzine_id_list","value": ""},{"key": "kw","value": "+++++"},{"key": "itemType","value": "0"},{"key": "source","value": "1"}]}
    [2018-04-12 16:01:01] DEBUG CampaignEngine:218 - enter CampaignEngine.getAppMiguSearch request:{"bookType":"0","categoryType":"0","chargeMode":"0","ct":"4","isContinue":"0","isNeedCorrect":"0","pageIndex":1,"pageNum":1,"phoneNumber":"15077870000","queryWord":"+++++","searchType":"0","sorter":"0","wordCount":"0"}
    [2018-04-12 16:01:08] DEBUG ResourceLeakDetectorFactory:76 - Loaded default ResourceLeakDetector: io.netty.util.ResourceLeakDetector@62c3bb38
    [2018-04-12 16:01:26] ERROR ServiceMethodImpl:101 - invoke failed, request_id = 601424C46E1591BD5BC9D9590C8EB793, sc={ rpc: { cname: ms.search.searchService:1.0.0.dev, method: getSearchRank, operation: ms.search.searchService:1.0.0.dev/getSearchRank}, implVersion: 0, complete: false, canceled: false}, ex=java.lang.reflect.InvocationTargetException
    [2018-04-12 16:01:34] DEBUG MsCommonAspect:84 - Enter setActionCdrParamAspect,identityId:15077870000
    [2018-04-12 16:01:34] DEBUG MsCommonAspect:108 - Exit setActionCdrParamAspect,identityId:15077870000,ActionCdrParams:
    [2018-04-12 16:01:34] INFO  MethodInvoker:151 - call: ms.search.searchService/getSearchRank, request_id=601424C46E1591BD5BC9D9590C8EB793, expected= 0, version=0, code=1, rt=33763132
    [2018-04-12 16:01:34] DEBUG EnvironmentInterceptor:55 - EnvironmentInterceptor completed, requesturl= http://127.0.0.1:8080/ms-search-war/ms.search.searchService/getSearchRank and server delayTime = 34008
    [2018-04-12 16:03:01] DEBUG AbstractPool:114 - server removed, node=ZJHZ-CMREAD-TEST213:8080, server={ node: ZJHZ-CMREAD-TEST213:8080, hostname: ZJHZ-CMREAD-TEST213, port: 8080, status: 8, weight: 8, capacity: 128, breaker: { state :CLOSED, working: 0, delay: 15000000000, failureThreshold: [80/100, 0.8], successThreshold: [12/16, 0.75]}, version: 2447894418 }
    [2018-04-12 16:03:45] DEBUG AbstractPool:107 - server updated, node=ZJHZ-CMREAD-TEST213:8080, server={ node: ZJHZ-CMREAD-TEST213:8080, hostname: ZJHZ-CMREAD-TEST213, port: 8080, status: 1, weight: 8, capacity: 128, breaker: { state :CLOSED, working: 0, delay: 15000000000, failureThreshold: [80/100, 0.8], successThreshold: [12/16, 0.75]}, version: 2447934730 }

      从日志里其实看不出啥来,通过eclipse调试才发现,进入这一行代码后发生了诡异的事情:

    String response = HttpTools.getDataFromFirstSerach(uri.toString(), jsonData);

      诡异的事情就是直接跳入异常了

        /**
         * Constructs a InvocationTargetException with a target exception.
         *
         * @param target the target exception
         */
        public InvocationTargetException(Throwable target) {
            super((Throwable)null);  // Disallow initCause
            this.target = target;
        }

      从异常看,是程序找不到调用代码了,但是eclipse里面是可以找到HttpTools类和getDataFromFirstSerach方法的。想了一下,应该是编译出问题了,class文件里没有对应的代码。直接到eclipse部署的本地tomcat路径下找class:打开Servers窗口 -> 找到Server Locations,看Server path和Deploy path,这里就是本地eclipse部署路径:

      进入本地部署路径:

      进入wtpwebapps -> 进入war包 -> 找到引用的jar -> 打开jar包,根据包路径找类,果然没有HttpTools这个类。估计是之前引用的jar包编译出了问题,没编译完。

      解决办法:重新编译问题jar包,刷新引用jar包的工程,最后clean一下tomcat,再次进入到tomcat本地部署路径E:workspace.metadata.pluginsorg.eclipse.wst.server.core mp0wtpwebapps,发现HttpTools类出现了,重新启动tomcat,问题依然存在。这就奇怪了,重新调试,发现另一个问题:

    java.lang.NoSuchMethodError: io.netty.handler.ssl.SslContextBuilder.protocols([Ljava/lang/String;)Lio/netty/handler/ssl/SslContextBuilder;

      有点摸不着头脑,代码调试时流程没变,直接跳入异常,但异常显示的是上面的问题,找不到SslContextBuilder.protocols这个方法。

      问题定位:仔细看了下HttpTools这个类:

    import java.io.IOException;
    import java.nio.charset.Charset;
    import java.util.Date;
    
    import javax.servlet.http.HttpServletRequest;
    
    import org.apache.http.HttpEntity;
    import org.apache.http.client.config.RequestConfig;
    import org.apache.http.client.methods.CloseableHttpResponse;
    import org.apache.http.client.methods.HttpPost;
    import org.apache.http.entity.StringEntity;
    import org.apache.http.impl.client.CloseableHttpClient;
    import org.apache.http.impl.client.HttpClients;
    import org.apache.http.util.EntityUtils;
    import org.apache.logging.log4j.LogManager;
    import org.apache.logging.log4j.Logger;
    import org.asynchttpclient.AsyncHttpClient;
    import org.asynchttpclient.AsyncHttpClientConfig;
    import org.asynchttpclient.BoundRequestBuilder;
    import org.asynchttpclient.DefaultAsyncHttpClient;
    import org.asynchttpclient.DefaultAsyncHttpClientConfig;
    import org.asynchttpclient.Response;
    import org.springframework.http.HttpStatus;/**
     * 基础的http方法 此类包含了通过HTTP协议的请求响应等方法。 此类属于公共方法,负责提供接口给业务调用。 目前请求支持4种请求方式: 1 xml请求报文通过POST方法请求URL,返回响应报文 2
     * xml请求报文通过PUT方法请求URL,返回响应报文 3 无参数的请求通过GET方法请求URL,返回响应报文 4 无参数的请求通过DELETE方法请求URL,返回操作结果 使用方法: 先得到HttpUtil的实例(
     * 使用连接池方式),通过HttpUtil.getInstance().getHttpClient()返回HttpClient对象 然后调用指定的方法即可
     * 
     */
    public class HttpTools
    {/**
         * HttpUtil类构造函数
         */
        public HttpTools()
        {
            
        }
        
        /**
         * 单例模式返回唯一的HttpUtil的实例 在创建HttpUtil实例的时候创建HttpClient对象,并且设置HttpClient超时的属性。
         * 创建HttpClient实例,默认是SimpleHttpConnectionManager创建的,不支持多线程。 使用多线程技术就是说,client可以在多个线程中被用来执行多个方法。
         * 每次调用HttpClient.executeMethod() 方法,都会去链接管理器申请一个连接实例, 申请成功这个链接实例被签出(checkout),随之在链接使用完后必须归还管理器。 管理器支持两个设置:
         * maxConnectionsPerHost 每个主机的最大并行链接数,默认为2 maxTotalConnections 客户端总并行链接最大数,默认为20
         * 
         * @return HttpUtil
         */
        public static HttpTools getInstance()
        {
            return instance;
        }
        
        private static AsyncHttpClient asynHttpClient = getAsyncHttpClient();
        
        /**
         * 获取请求类的客户端
         * 
         * @return
         */
        public static AsyncHttpClient getAsyncHttpClient()
        {
            AsyncHttpClientConfig config = new DefaultAsyncHttpClientConfig.Builder().setFollowRedirect(false)
                .setConnectTimeout(PropertiesConfig.getInt("asynHttp.connectTimeout", 500))
                .setRequestTimeout(PropertiesConfig.getInt("asynHttp.requestTimeout", 10000))
                .setReadTimeout(PropertiesConfig.getInt("asynHttp.readTimeout", 10000))
                .build();
            AsyncHttpClient client = new DefaultAsyncHttpClient(config);
            return client;
        }
         public static String getDataFromFirstSerach(String uri,String  jsonParam)
        {
            long startTime = System.currentTimeMillis();
            String responseContent = null;
            CloseableHttpResponse response = null;
            
            // 设置超时
            RequestConfig requestConfig = RequestConfig.custom()
                .setSocketTimeout(PropertiesConfig.getInt("timeOut", 15000))
                .setConnectTimeout(PropertiesConfig.getInt("timeOut", 15000))
                .setConnectionRequestTimeout(PropertiesConfig.getInt("timeOut", 15000))
                .build();
            
            // 调用一级搜索接口
            CloseableHttpClient httpClient = HttpClients.createDefault();
            try
            {
                
                StringEntity entity = new StringEntity(jsonParam, "utf-8");// 解决中文乱码问题
                entity.setContentEncoding("UTF-8");
                entity.setContentType("application/json");
                String timeTapms = new Date().getTime() + "";
                String mgauth = PropertiesConfig.getProperty(APPID) + PropertiesConfig.getProperty(APPKEY) + timeTapms;
                HttpPost httpPost = new HttpPost(uri);
                httpPost.setConfig(requestConfig);
                httpPost.setEntity(entity);
                httpPost.addHeader(ParamConstants.MGAUTH, CipherOperateUtil.encryptByMD5(mgauth));
                httpPost.addHeader(ParamConstants.REQTIME, timeTapms);
                response = httpClient.execute(httpPost);
                
                // 请求响应成功则获取响应
                if (null != response && response.getStatusLine().getStatusCode() == 200)
                {
                    HttpEntity httpentity = response.getEntity();
                    responseContent = EntityUtils.toString(httpentity, "UTF-8");
                }
            }
            catch (Exception e)
            {
                logger.error(" httpTools.getDataFromFirstSerach call classifySearch error message:{},request:{} ",e.getMessage(),jsonParam);
            }
            finally
            {
                try
                {
                    // 关闭连接,释放资源
                    if (response != null)
                    {
                        response.close();
                    }
                    if (httpClient != null)
                    {
                        httpClient.close();
                    }
                }
                catch (IOException e)
                {
                    logger.error(" httpTools.getDataFromFirstSerach call classifySearch error message:{},request:{} ",e.getMessage(),jsonParam);
                }
            }
            long endTime = System.currentTimeMillis();
    
            if (logger.isDebugEnabled())
            {
                logger.debug("=======================call getDataFromFirstSerach interface = {},spend time = {} ms",
                    uri,
                    endTime - startTime);
            }
    
            return responseContent;
        }
        
    }

      从上面看出,类一开始就通过静态方法getAsyncHttpClient得到异步请求的客户端对象asynHttpClient,所以应该在getAsyncHttpClient方法加断点,否则就直接进入异常了。异常跟踪直接看日志:

    ERROR StatusLogger No log4j2 configuration file found. Using default configuration: logging only errors to the console. Set system property 'log4j2.debug' to show Log4j2 internal initialization logging.
    Exception in thread "main" java.lang.NoSuchMethodError: io.netty.handler.ssl.SslContextBuilder.protocols([Ljava/lang/String;)Lio/netty/handler/ssl/SslContextBuilder;
        at org.asynchttpclient.netty.ssl.DefaultSslEngineFactory.buildSslContext(DefaultSslEngineFactory.java:45)
        at org.asynchttpclient.netty.ssl.DefaultSslEngineFactory.init(DefaultSslEngineFactory.java:69)
        at org.asynchttpclient.netty.channel.ChannelManager.<init>(ChannelManager.java:110)
        at org.asynchttpclient.DefaultAsyncHttpClient.<init>(DefaultAsyncHttpClient.java:85)
        at cn.migu.newportal.cache.util.HttpTools.getAsyncHttpClient(HttpTools.java:109)
        at cn.migu.newportal.cache.util.HttpTools.<clinit>(HttpTools.java:95)

      DefaultAsyncHttpClient:

        /**
         * Create a new HTTP Asynchronous Client using the specified
         * {@link DefaultAsyncHttpClientConfig} configuration. This configuration
         * will be passed to the default {@link AsyncHttpClient} that will be
         * selected based on the classpath configuration.
         *
         * @param config a {@link DefaultAsyncHttpClientConfig}
         */
        public DefaultAsyncHttpClient(AsyncHttpClientConfig config) {
    
            this.config = config;
    
            allowStopNettyTimer = config.getNettyTimer() == null;
            nettyTimer = allowStopNettyTimer ? newNettyTimer() : config.getNettyTimer();
    
            channelManager = new ChannelManager(config, nettyTimer);
            requestSender = new NettyRequestSender(config, channelManager, nettyTimer, new AsyncHttpClientState(closed));
            channelManager.configureBootstraps(requestSender);
        }

      ChannelManager:

        public ChannelManager(final AsyncHttpClientConfig config, Timer nettyTimer) {
    
            this.config = config;
            this.sslEngineFactory = config.getSslEngineFactory() != null ? config.getSslEngineFactory() : new DefaultSslEngineFactory();
            try {
                this.sslEngineFactory.init(config);
            } catch (SSLException e) {
                throw new RuntimeException("Could not initialize sslEngineFactory", e);
            }
    
            ChannelPool channelPool = config.getChannelPool();
            if (channelPool == null) {
                if (config.isKeepAlive()) {
                    channelPool = new DefaultChannelPool(config, nettyTimer);
                } else {
                    channelPool = NoopChannelPool.INSTANCE;
                }
            }
            this.channelPool = channelPool;
    
            
            tooManyConnections = unknownStackTrace(new TooManyConnectionsException(config.getMaxConnections()), ChannelManager.class, "acquireChannelLock");
            tooManyConnectionsPerHost = unknownStackTrace(new TooManyConnectionsPerHostException(config.getMaxConnectionsPerHost()), ChannelManager.class, "acquireChannelLock");
            maxTotalConnectionsEnabled = config.getMaxConnections() > 0;
            maxConnectionsPerHostEnabled = config.getMaxConnectionsPerHost() > 0;
    
            if (maxTotalConnectionsEnabled || maxConnectionsPerHostEnabled) {
                openChannels = new DefaultChannelGroup("asyncHttpClient", GlobalEventExecutor.INSTANCE) {
                    @Override
                    public boolean remove(Object o) {
                        boolean removed = super.remove(o);
                        if (removed) {
                            if (maxTotalConnectionsEnabled)
                                freeChannels.release();
                            if (maxConnectionsPerHostEnabled) {
                                Object partitionKey = channelId2PartitionKey.remove(Channel.class.cast(o));
                                if (partitionKey != null) {
                                    Semaphore hostFreeChannels = freeChannelsPerHost.get(partitionKey);
                                    if (hostFreeChannels != null)
                                        hostFreeChannels.release();
                                }
                            }
                        }
                        return removed;
                    }
                };
                freeChannels = new Semaphore(config.getMaxConnections());
            } else {
                openChannels = new DefaultChannelGroup("asyncHttpClient", GlobalEventExecutor.INSTANCE);
                freeChannels = null;
            }
    
            handshakeTimeout = config.getHandshakeTimeout();
    
            // check if external EventLoopGroup is defined
            ThreadFactory threadFactory = config.getThreadFactory() != null ? config.getThreadFactory() : new DefaultThreadFactory(config.getThreadPoolName());
            allowReleaseEventLoopGroup = config.getEventLoopGroup() == null;
            ChannelFactory<? extends Channel> channelFactory;
            if (allowReleaseEventLoopGroup) {
                if (config.isUseNativeTransport()) {
                    eventLoopGroup = newEpollEventLoopGroup(config.getIoThreadsCount(), threadFactory);
                    channelFactory = getEpollSocketChannelFactory();
    
                } else {
                    eventLoopGroup = new NioEventLoopGroup(config.getIoThreadsCount(), threadFactory);
                    channelFactory = NioSocketChannelFactory.INSTANCE;
                }
    
            } else {
                eventLoopGroup = config.getEventLoopGroup();
                if (eventLoopGroup instanceof OioEventLoopGroup)
                    throw new IllegalArgumentException("Oio is not supported");
    
                if (eventLoopGroup instanceof NioEventLoopGroup) {
                    channelFactory = NioSocketChannelFactory.INSTANCE;
                } else {
                    channelFactory = getEpollSocketChannelFactory();
                }
            }
    
            httpBootstrap = newBootstrap(channelFactory, eventLoopGroup, config);
            wsBootstrap = newBootstrap(channelFactory, eventLoopGroup, config);
    
            // for reactive streams
            httpBootstrap.option(ChannelOption.AUTO_READ, false);
        }

      DefaultSslEngineFactory:

    package org.asynchttpclient.netty.ssl;
    
    import io.netty.buffer.ByteBufAllocator;
    import io.netty.handler.ssl.SslContext;
    import io.netty.handler.ssl.SslContextBuilder;
    import io.netty.handler.ssl.SslProvider;
    import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
    import java.util.Arrays;
    import javax.net.ssl.SSLEngine;
    import javax.net.ssl.SSLException;
    import org.asynchttpclient.AsyncHttpClientConfig;
    import org.asynchttpclient.util.MiscUtils;
    
    public class DefaultSslEngineFactory
      extends SslEngineFactoryBase
    {
      private volatile SslContext sslContext;
      
      private SslContext buildSslContext(AsyncHttpClientConfig config)
        throws SSLException
      {
        if (config.getSslContext() != null) {
          return config.getSslContext();
        }
        SslContextBuilder sslContextBuilder = SslContextBuilder.forClient().sslProvider(config.isUseOpenSsl() ? SslProvider.OPENSSL : SslProvider.JDK).sessionCacheSize(config.getSslSessionCacheSize()).sessionTimeout(config.getSslSessionTimeout());
        if (MiscUtils.isNonEmpty(config.getEnabledProtocols())) {
          sslContextBuilder.protocols(config.getEnabledProtocols());
        }
        if (MiscUtils.isNonEmpty(config.getEnabledCipherSuites())) {
          sslContextBuilder.ciphers(Arrays.asList(config.getEnabledCipherSuites()));
        }
        if (config.isUseInsecureTrustManager()) {
          sslContextBuilder.trustManager(InsecureTrustManagerFactory.INSTANCE);
        }
        return configureSslContextBuilder(sslContextBuilder).build();
      }
      
      public SSLEngine newSslEngine(AsyncHttpClientConfig config, String peerHost, int peerPort)
      {
        SSLEngine sslEngine = this.sslContext.newEngine(ByteBufAllocator.DEFAULT, peerHost, peerPort);
        configureSslEngine(sslEngine, config);
        return sslEngine;
      }
      
      public void init(AsyncHttpClientConfig config)
        throws SSLException
      {
        this.sslContext = buildSslContext(config);
      }
      
      protected SslContextBuilder configureSslContextBuilder(SslContextBuilder builder)
      {
        return builder;
      }
    }

      DefaultSslEngineFactory调用SslContextBuilder.protocols方法时找不到了,进入SslContextBuilder类,确实没有protocols方法。去看了下pomx.ml文件,引入的async-http-client.jar是2.1.0-alpha26版本,匹配的netty-all.jar却是4.1.8.Final版本的,而这个版本的SslContextBuilder并不存在protocols方法。

      问题解决:使用2.1.0-alpha6版本async-http-client的jar包,这个包里DefaultSslEngineFactory没有调用SslContextBuilder.protocols方法:

     * Copyright (c) 2015 AsyncHttpClient Project. All rights reserved.
    package org.asynchttpclient.netty.ssl;
    
    import io.netty.buffer.ByteBufAllocator;
    import io.netty.handler.ssl.SslContext;
    import io.netty.handler.ssl.SslContextBuilder;
    import io.netty.handler.ssl.SslProvider;
    
    import javax.net.ssl.SSLEngine;
    import javax.net.ssl.SSLException;
    
    import org.asynchttpclient.AsyncHttpClientConfig;
    
    public class DefaultSslEngineFactory extends SslEngineFactoryBase {
    
        private volatile SslContext sslContext;
    
        private SslContext buildSslContext(AsyncHttpClientConfig config) throws SSLException {
            if (config.getSslContext() != null)
                return config.getSslContext();
    
            SslContextBuilder sslContextBuilder = SslContextBuilder.forClient()//
                    .sslProvider(config.isUseOpenSsl() ? SslProvider.OPENSSL : SslProvider.JDK)//
                    .sessionCacheSize(config.getSslSessionCacheSize())//
                    .sessionTimeout(config.getSslSessionTimeout());
    
            if (config.isUseInsecureTrustManager()) {
                sslContextBuilder.trustManager(InsecureTrustManagerFactory.INSTANCE);
            }
    
            return configureSslContextBuilder(sslContextBuilder).build();
        }
    
        @Override
        public SSLEngine newSslEngine(AsyncHttpClientConfig config, String peerHost, int peerPort) {
            // FIXME should be using ctx allocator
            SSLEngine sslEngine = sslContext.newEngine(ByteBufAllocator.DEFAULT, peerHost, peerPort);
            configureSslEngine(sslEngine, config);
            return sslEngine;
        }
    
        @Override
        public void init(AsyncHttpClientConfig config) throws SSLException {
            sslContext = buildSslContext(config);
        }
    
        /**
         * The last step of configuring the SslContextBuilder used to create an SslContext when no context is provided in
         * the {@link AsyncHttpClientConfig}. This defaults to no-op and is intended to be overridden as needed.
         *
         * @param builder builder with normal configuration applied
         * @return builder to be used to build context (can be the same object as the input)
         */
        protected SslContextBuilder configureSslContextBuilder(SslContextBuilder builder) {
            // default to no op
            return builder;
        }
    
    }

      重启后测试通过,问题解决。

  • 相关阅读:
    新概念第二册(1)--英语口语听力课1
    外企面试课程(一)---熟悉常见的缩略词
    公司 邮件 翻译 培训 长难句 结课
    workflow
    公司 邮件 翻译 培训 长难句 20
    公司 邮件 翻译 培训 长难句 19
    Engineering Management
    公司 邮件 翻译 培训 长难句 18
    公司 邮件 翻译 培训 长难句 17
    第14.5节 利用浏览器获取的http信息构造Python网页访问的http请求头
  • 原文地址:https://www.cnblogs.com/wuxun1997/p/8808604.html
Copyright © 2011-2022 走看看