• HttpUrlConnection get和post简单实现(疑惑解决)


    近期研究微信的公众平台开发。须要和微信的server进行数据读取,简单研究了下jdk自带的HttpUrlConnection类(URLConnection的子类),简单实现了一下微信的access_token获取。

    获取微信access_token的地址:微信地址

    该地址是get方法请求就可以。先贴代码,doGet:

    private final String ACCESS_TOKEN_WEIXIN = "https://api.weixin.qq.com/cgi-bin/token";
    	
    	public String doGet(String grantType, String appId, String secret){
    		String url = ACCESS_TOKEN_WEIXIN+"?grant_type="+grantType+
    				"&appid="+appId+"&secret="+secret;
    		HttpURLConnection conn = null;
    		InputStream is = null;
    		InputStreamReader reader = null;
    		BufferedReader br = null;
    		String str = "";
    		try {
    			URL weiUrl = new URL(url);
    			conn = (HttpURLConnection)weiUrl.openConnection();
    			conn.setRequestProperty("connection", "Keep-Alive");
    			conn.connect();
    			is = conn.getInputStream();
    			reader = new InputStreamReader(is, "UTF-8");
    			br = new BufferedReader(reader);
    			String readLine = "";
    			while((readLine=br.readLine())!=null){
    				str+=readLine+"
    ";
    			}
    		} catch (Exception e) {
    			e.printStackTrace();
    		}finally{
    			try{
    				if(br!=null){
    					br.close();
    				}
    				if(conn!=null){
    					conn.disconnect();
    				}
    			}catch(Exception e1){
    				e1.printStackTrace();
    			}
    		}
    		return str;
    	}

    我将InputStream流和InputStreamReader流都单独定义引用是为了測试流关闭的问题,最后仅仅关闭最外层流,由于外层流关闭的时候就将包裹的流一并关闭了。见以下的BufferedReader类的close方法:

    public void close() throws IOException {
            synchronized (lock) {
                if (in == null)
                    return;
                in.close();
                in = null;
                cb = null;
            }
        }
    当中,in就是包裹的流。


    以下贴post方法的代码。然后综合比較一下,代码:

    public String doPost(String grantType, String appId, String secret){
    		HttpURLConnection conn = null;
    		InputStream is = null;
    		InputStreamReader reader = null;
    		BufferedReader br = null;
    		String str = "";
    		try {
    			URL url = new URL(ACCESS_TOKEN_WEIXIN);
    			conn = (HttpURLConnection)url.openConnection();
    			conn.setRequestMethod("POST");
    			conn.setDoOutput(true);//默觉得false的,所以须要设置
    			conn.setUseCaches(false);
    			conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
    			conn.connect();
    			
    			OutputStream outStream = conn.getOutputStream();
    			DataOutputStream out = new DataOutputStream(outStream);
    			String params = "grant_type="+grantType+
    					"&appid="+appId+"&secret="+secret;
    			out.writeBytes(params);
    			out.close();
    			
    			is = conn.getInputStream();
    			reader = new InputStreamReader(is, "UTF-8");
    			br = new BufferedReader(reader);
    			String readLine = "";
    			while((readLine=br.readLine())!=null){
    				str+=readLine+"
    ";
    			}
    		} catch (Exception e) {
    			e.printStackTrace();
    		}finally{
    			try{
    				if(br!=null){
    					br.close();
    				}
    				if(conn!=null){
    					conn.disconnect();
    				}
    			}catch(Exception e1){
    				e1.printStackTrace();
    			}
    		}
    		return str;
    	}

    两相比較:

    1.思路是一样的:首先建立链接对象(URL)->由链接对象得到代理对象(HttpUrlConnection)->设置一些链接參数->启动链接通道(conn.connect();)->进行输出输入操作

    2.get方法參数是带在链接上的,所以没有单独赋值操作。直接在启动链接通道后得到输入流就可以得到返回值

    3.post方法參数不带在链接上,须要单独赋值(赋值是使用输出流来进行的)。须要将输出打开(conn.setDoOutput(true);)由于URLConnection默认输出是false。而input是默认打开的。故不用反复设置。

    4.读取返回值方式一样。


    在研究过程中,对于URLConnection如何得到的InputStream比較好奇。查看源代码为:

    public InputStream getInputStream() throws IOException {
            throw new UnknownServiceException("protocol doesn't support input");
        }

    所以非常迷惑,是jdk1.7 。等下来研究出来再补上来。

    共勉!

    -----------------------------------------------------------------

    紧跟上面。对于jdk如何实现的getInputStream和getOutputStream以及其它一些方法,经过研究。最终找到了。接下来释疑:

    须要注意到:

    URL url = new URL(ACCESS_TOKEN_WEIXIN);
    			conn = (HttpURLConnection)url.openConnection();


    这里得到HttpUrlConnection的时候是使用了强转得到的,故跟踪到URL类的openConnection方法内->

    public URLConnection openConnection() throws java.io.IOException {
            return handler.openConnection(this);
        }


    这里的handler定义在URL类的上方->

    transient URLStreamHandler handler;

    然后去寻找handler的赋值方法getURLStreamHandler。发现例如以下代码->

    if (handler == null) {
                    String packagePrefixList = null;
    
                    packagePrefixList
                        = java.security.AccessController.doPrivileged(
                        new sun.security.action.GetPropertyAction(
                            protocolPathProp,""));
                    if (packagePrefixList != "") {
                        packagePrefixList += "|";
                    }
    
                    // REMIND: decide whether to allow the "null" class prefix
                    // or not.
                    packagePrefixList += JDK_PACKAGE_PREFIX;
    
                    StringTokenizer packagePrefixIter =
                        new StringTokenizer(packagePrefixList, "|");
    
                    while (handler == null &&
                           packagePrefixIter.hasMoreTokens()) {
    
                        String packagePrefix =
                          packagePrefixIter.nextToken().trim();
    
                        // do not try to instantiate the JDK gopher handler
                        // unless the system property had been explicitly set
                        if (protocol.equalsIgnoreCase(GOPHER) &&
                            packagePrefix.equals(JDK_PACKAGE_PREFIX) &&
                            !enableGopher) {
                                continue;
                        }
                        try {
                            String clsName = packagePrefix + "." + protocol +
                              ".Handler";
                            Class cls = null;
                            try {
                                cls = Class.forName(clsName);

    当中
    JDK_PACKAGE_PREFIX
    在类中定义的是:

     private static final String JDK_PACKAGE_PREFIX =  "sun.net.www.protocol";
    再结合以下的
     String clsName = packagePrefix + "." + protocol +
                              ".Handler";
    基本能够定位到handler类的实现类在sun.net.www.protocol.http.Handler。

    然后百度搜索之后找到源代码(jdk下没有sun包的源代码)->

    sun.net.www.protocol.http包的视图  Handler.java的源代码 ->

    protected java.net.URLConnection openConnection(URL u)
        throws IOException {
            return openConnection(u, (Proxy)null);
        }
    
        protected java.net.URLConnection openConnection(URL u, Proxy p)
            throws IOException {
            return new HttpURLConnection(u, p, this);
        }

    能够看到返回值是HttpURLConnection,可是发现该类中并没有引入java.net.HttpURLConnection类,故推測在Handler的同包下有一个同名类,果然发现该类,源代码 ->

    public class HttpURLConnection extends java.net.HttpURLConnection

    这是关建行,该类继承了java.net.HttpURLConnection类。所以返回该类能够由java.net.HttpURLConnection类的父类URLConnection接到,然后强转成java.net.HttpURLConnection类。设计十分巧妙->

    @Override
        public synchronized OutputStream getOutputStream() throws IOException {
    
            try {
                if (!doOutput) {
                    throw new ProtocolException("cannot write to a URLConnection"
                                   + " if doOutput=false - call setDoOutput(true)");
                }
    
                if (method.equals("GET")) {
                    method = "POST"; // Backward compatibility
                }
                if (!"POST".equals(method) && !"PUT".equals(method) &&
                    "http".equals(url.getProtocol())) {
                    throw new ProtocolException("HTTP method " + method +
                                                " doesn't support output");
                }
    
                // if there's already an input stream open, throw an exception
                if (inputStream != null) {
                    throw new ProtocolException("Cannot write output after reading input.");
                }
    
                if (!checkReuseConnection())
                    connect();
    ......

    这就是getOutputStream的实现地方了,正好doOutput属性的设置也在这里起到了作用。

    艾玛,最终找到实现的地方了。其它方法原理一致了。

    略微整理下:整个的类流转过程就是:sun.net.www.protocol.http.HttpURLConnection转化成java.net.URLConnection,然后强转成java.net.HttpURLConnection。

    晕乎~





  • 相关阅读:
    招聘ASP.NET(C#)开发人员(已经截止,谢谢大家支持)
    VisualStudioCode开发Vue
    全局异常处理机制(Filter拦截器对比)
    工程师
    kubernetes(k8s)里面部署服务器集群并访问项目
    webpack 就是前端模块化打包工具
    Visual Studio Code配置C/C++开发环境
    docker和k8s(Kubernetes)是配套使用
    kettle 多表全删全插同步数据
    wireshark 抓HTTPS 的包 HTTPS = TLS + HTTP TLSv1.2 协议
  • 原文地址:https://www.cnblogs.com/mfrbuaa/p/5196515.html
Copyright © 2020-2023  润新知