<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    blog.Toby

      BlogJava :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理 ::
      130 隨筆 :: 2 文章 :: 150 評論 :: 0 Trackbacks
    本文首先討論了Web服務會話狀態的保持方法,然后著重結合J2EE平臺中Web服務核心技術--JAX-RPC來介紹怎么在Web服務調用過程中保持客戶端的會話狀態,并且提供了服務端和不同類型客戶端的調用實例。

    本文是J2EE Web服務開發系列文章的第九篇,本文首先討論了Web服務會話狀態的保持方法,然后著重結合J2EE平臺中Web服務核心技術--JAX-RPC來介紹怎么在Web服務調用過程中保持客戶端的會話狀態,并且提供了服務端和不同類型客戶端的調用實例。

    閱讀本文前您需要以下的知識和工具:

    • J2EESDK1.4(Sun已經發布了正式版),并且會初步使用;

    • 了解JAX-RPC的基本概念;
    • 能夠使用JAX-RPC技術開發Web服務;
    • 一般的Java編程知識。

    本文的參考資料見 參考資料

    本文的全部代碼在這里 下載

    Web服務與會話


    Web服務大多基于HTTP協議,而HTTP協議是一種無狀態的協議。Web服務規范并沒有定義客戶端和服務端之間會話的保持方法。所以要在多個Web服務調用之間保持一些狀態,需要使用一些額外的技術或者方法。

    我們知道,基于HTTP的應用開發中,要在多個調用之間保持會話狀態,通常可以采用以下幾種方式:

    • URL重寫,把要傳遞的參數重寫在URL中;

    • 使用Cookie,把要傳遞的參數寫入到客戶端cookie中;

    • 使用隱含表單,把要傳遞的參數寫入到隱含的表單中;

    • 使用Session,把要傳遞的參數保存在session對象中(其實Session機制基于cookie或者URL重寫)。

    上面幾個方式有一個共同點:把要傳遞的參數保存在兩個頁面都能共享的對象中,前一個頁面在這個對象中寫入狀態、后一個頁面從這個對象中讀取狀態。特別是對于使用session方式,每個客戶端在服務端都對應了一個sessionid,服務端維持了由sessionid標識的一系列session對象,而session對象用于保持共享的信息。

    我們似乎從上面得到一些啟發,是否可以在服務端標識每個Web服務客戶端,并且把它們的狀態保持在某個可以共享的位置,比如內存、文件系統、數據庫。是的,許多Web服務開發工具正是這樣實現的。如果使用Weblogic Workshop開發Web服務,它可以把Web服務的狀態保持在實體Bean中。

    如果Web服務的服務端能夠訪問HTTP會話對象,那么就可以通過HTTP會話來支持Web服務的會話。JAX-RPC就是采用了這種方式。

    如果Web服務技術或者開發工具沒有提供任何的支持,那么我們可以在服務端維持一個客戶端狀態池,這個狀態池中的對象由客戶端的id標識,客戶端在每次調用時,都使用以下格式的SOAP消息:

    												
    														<soapenv:Envelope 
    xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
    xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
     <soapenv:Body>
       ...
       <client-id>001sf3242x-234234</client-id>
       <call-params>… </call-param>
       <call-method>
        getLogCount
       </call-method>
     </soapenv:Body>
    </soapenv:Envelope>
    
    												
    										

    服務端接收到這個SOAP消息時,可以通過這個<client-id>來獲得對應狀態池中的對象,然后進一步獲得客戶端預先設置的狀態信息。

    下面結合JAX-RPC技術來討論具體的實現方法。





    回頁首


    JAX-RPC和Web服務會話


    概念回顧:在J2EE平臺中,要開發Web服務,可以使用兩種技術:JAX-RPC和JAXM。而對于JAX-RPC,又有兩種不同類型的服務端點:Servlet服務端點和EJB服務端點。基于Servlet的服務端點運行在Servlet容器中,基于EJB的服務端點運行在EJB容器中。

    我們知道,Servlet可以在客戶端的多個調用之間保持會話狀態,所以基于Servlet的JAX-RPC Web服務端點要保持客戶的會話狀態是可行的。但如果是EJB服務端點,由于這里的EJB是無狀態會話Bean,所以要在多個調用之間保持狀態必須通過其它機制實現,這里不討論。

    下面從JAX-RPC的生命周期和ServletEndpointContext接口來說明怎么保持和訪問客戶端的會話狀態。

    JAX-RPC的生命周期

    根據JAX-RPC的規范,如果Web服務端點實現javax.xml.rpc.server.ServiceLifecycle接口,那么基于Servlet容器的JAX-RPC運行環境將管理這個端點的生命周期。javax.xml.rpc.server.ServiceLifecycle接口定義如下:

    												
    														例程1 ServiceLifecycle
    package javax.xml.rpc.server;
    public interface ServiceLifecycle {
    void init(Object context) throws ServiceException;
    void destroy();
    }
    
    												
    										

    JAX-RPC運行環境負責裝載并實例化服務端點實例,裝載和實例化可以在JAX-RPC運行環境啟動時進行,也可以在服務端點處理SOAP RPC請求時進行。JAX-RPC運行環境使用Java類裝載機制來裝載服務端點,當成功裝載目標類后,將實例化這個類。

    當服務端點實例化后,在RPC請求到達之前JAX-RPC運行環境將初始化它們,這個初始化通過ServiceLifecycle.init方法來進行的。在初始化的過程中,可能需要設置一些訪問外部資源的方法。在init方法中,有一個context參數,它用來訪問由JAX-RPC運行環境提供的端點上下文(ServletEndpoint Context)。當初始化服務端點后,JAX-RPC運行環境就可以把多個遠程調用派發到服務端點。在遠程方法調用的過程中,JAX-RPC服務端點并不維持任何客戶端的狀態。所以JAX-RPC服務端點實例能夠被池化(Pooling)。

    當JAX-RPC運行環境決定移除服務端點實例時,它將調用服務端點實例的destroy方法。比如在系統關閉或者實例池中實例過多時,就可能發生這種操作。在destroy方法中,服務端點將釋放占用的資源。當成功調用了destroy方法后,服務端點實例將被垃圾收集器收集。此時它不能處理任何遠程方法調用。

    ServletEndpointContext接口

    在ServiceLifecycle.init(Object context)方法中,其中參數context就是ServletEndpointContext實例,ServletEndpointContext是由JAX-RPC運行環境維持的端點上下文。JAX-RPC規范規定了基于Servlet的服務端點的編程模型,但并沒有為服務端點上下文(Endpoint Context)或者會話(Session)定義與組件模型、容器、綁定協議更通用的抽象,也就是說這些通用的抽象在JAX-RPC規范之外。

    下面是ServletEndpointContext接口的代碼。

    												
    														例程2 ServletEndpointContext接口
    package javax.xml.rpc.server;
    public interface ServletEndpointContext {
    public java.security.Principal getUserPrincipal();
    public javax.xml.rpc.handler.MessageContext getMessageContext();
    public javax.servlet.http.HttpSession getHttpSession();
    public javax.servlet.ServletContext getServletContext();
    }
    
    												
    										

    ServletEndpointContext接口對于會話的保持非常關鍵,因為它定義了getHttpSession方法,這個方法返回了和當前活動的客戶端調用相關的HTTP會話。客戶端的會話由JAX-RPC運行環境維持。如果沒有相關的HTTP會話,那么這個方法返回null。

    除了getHttpSession方法外,這個接口還定義了或者SOAP消息上下文,Servlet上下文方法,在這里不討論了。

    有了上面的理論,下面我們來開發一個能夠使用HTTP會話的Web服務。





    回頁首


    開發服務端


    首先定義一個端點接口,它擁有幾個交互操作的方法,如例程3所示。

    												
    														例程3 定義服務端點接口
    package com.hellking.study.webservice.session;
    
    import java.rmi.Remote;
    import java.rmi.RemoteException;
    /**
     *Web服務端點接口,它定義了三個服務方法。
     */
    public interface SessionTestIF extends Remote {
        public String login(String id,String password) throws RemoteException;
        public String getLoginCount() throws RemoteException;    
        public void logout() throws RemoteException;
    }
    
    												
    										

    要想在Web服務中訪問HTTP會話,那么必須擁有ServletEndpointContext實例,而這個實例必須通過ServiceLifecycle接口的init方法獲得。也就是說,要使用HTTP會話,Web服務端點必須實現ServiceLifecycle接口。Web服務實現類如例程4所示。

    												
    														例程4 服務實現類
    package com.hellking.study.webservice.session;
    
    import java.rmi.Remote.*;
    import javax.xml.rpc.server.ServiceLifecycle;
    import javax.xml.rpc.server.ServletEndpointContext;
    import javax.xml.rpc.handler.soap.SOAPMessageContext;
    import java.util.Properties;
    import java.io.FileInputStream;
    import javax.servlet.http.HttpSession;
    /**
     *SessionTestImpl是Web服務實現類,用于測試Web服務中Session的使用。
     *由于要使用Session,需要實現ServiceLifecycle接口。
     */
    public class SessionTestImpl implements SessionTestIF,ServiceLifecycle{
     
     //服務端點上下文
     private ServletEndpointContext serviceContext;
        
        /**
         *ServiceLifecycle方法:初始化服務端點,或者要使用的資源。
         */
        public void init(java.lang.Object context)
        {
      serviceContext=(ServletEndpointContext)context;
     }
     /**
         *ServiceLifecycle方法:銷毀服務端點實例。
         */
        public void destroy() 
        {
         this.serviceContext=null;
        //可能還有其它釋放資源的方法。
        }
     /**
      *Web服務方法:登錄,并且保存一些信息到HTTP會話中。
      */
        public String login(String id,String password) 
        {
         String ret=null;//返回值。
         Properties users=new Properties();
         try
         {
          //獲得用戶名、密碼屬性,一般是在數據庫中,這里簡化,把這些信息保存在一個文件中。
            users.load(com.hellking.study.webservice.session.SessionTestImpl.class
              .getResourceAsStream("password.properties"));
         }
         catch(java.io.FileNotFoundException e)
         {
          e.printStackTrace();
         }
         catch(java.io.IOException e)
         {
          e.printStackTrace();
         }
         try
         {
          String passwd=(String)users.getProperty(id);
          if(password.equals(passwd))
          {
           ret="登錄成功,你可以執行其它操作!";
        HttpSession session = serviceContext.getHttpSession();
        //保存用戶會話狀態,它和一般的HTTP會話一樣,都使用HttpSession來進行。
        session.setAttribute("isLogin",new Boolean(true));
        session.setAttribute("userId",id);    
        //更新登錄次數,在這里省略。
          }
         }
         catch(Exception e)
         {
          ret="登錄失敗,請確認用戶名和密碼正確!";
          e.printStackTrace();
         } 
         return ret;
        }
        
        /**
         *Web服務方法:獲得登錄的次數,需要使用HTTP會話來獲得當前user的id。
         */
        public String getLoginCount() 
        {
         String ret=null;//返回值
         HttpSession session = serviceContext.getHttpSession();
            Properties logcount=new Properties();
         try
         {
          //獲得用戶登錄次數。
          logcount.load(com.hellking.study.webservice.session.SessionTestImpl.class
          .getResourceAsStream("login.properties"));
         }
         catch(java.io.FileNotFoundException e)
         {
          e.printStackTrace();
         }
         catch(java.io.IOException e)
         {
          e.printStackTrace();
         }
         try
         {
          //從HTTP會話中獲得是否登錄的屬性。
          Boolean isLogin=(Boolean)session.getAttribute("isLogin");
          String userId=(String)session.getAttribute("userId");
          //如果已經登錄,那么返回logcount屬性值。
          if(isLogin.equals(Boolean.TRUE))
          {      
           ret=logcount.getProperty(userId);
          }
         }
         catch(Exception e)
         {
          e.printStackTrace();
         }
         return ret;     
        }
        /**
         *注銷,使會話無效。
         */    
        public void logout() 
        {
         HttpSession session = serviceContext.getHttpSession();
         session.invalidate();
        }        
    }
    
    												
    										

    在上面代碼中,SessionTestImpl 擁有一個ServletEndpointContext成員變量,這個成員變量在init方法初始化:

    												
    														serviceContext=(ServletEndpointContext)context;
    
    												
    										

    在login方法中,通過:

    												
    														HttpSession session = serviceContext.getHttpSession();
    
    												
    										

    方法來獲得和當前客戶端關聯的HTTP會話,然后可以把一些交互的信息保存在session中,如:

    												
    														session.setAttribute("isLogin", new Boolean(true));
    
    												
    										

    把用戶已經登錄的信息保存起來。在其它的Web服務方法中,如getLoginCount,可以通過:

    												
    														Boolean isLogin=(Boolean)session.getAttribute("isLogin");
    
    												
    										

    之類的方法來獲得原來保存的屬性值。





    回頁首


    編寫描述符、部署


    開發好以上兩個類后,需要進行一些相關的描述,編寫以下腳本:

    												
    														例程5 service-config.xml 
    <?xml version="1.0" encoding="UTF-8"?>
    <configuration 
      xmlns="http://java.sun.com/xml/ns/jax-rpc/ri/config">
      <service 
          name="MySessionTestService" 
          targetNamespace="urn:SessionTest" 
          typeNamespace="urn:SessionTest" 
          packageName="sessionTest">
          <interface name="com.hellking.study.webservice.session.SessionTestIF"/>
      </service>
    </configuration>
    
    												
    										

    通過:

    												
    														wscompile -define -d . -nd . -classpath . service-config.xml
    
    												
    										

    命令生成一個名為MySessionTestService.wsdl的Web服務描述文件。再通過:

    												
    														wscompile -gen -classpath . -d . -nd . -mapping mapping.xml service-config.xml
    
    												
    										

    生成一個映射文件。另外,還需要編寫幾個描述符,如webservices.xml、web.xml等,在這里就不介紹了(這些描述符可以通過部署工具自動生成,見本文代碼)。由于這個服務端點需要維持會話,所以在web.xml中特別描述了會話的保持時間,如例程6所示。

    												
    														例程6 web.xml描述符
    <?xml version="1.0" encoding="UTF-8"?>
    
    <!DOCTYPE web-app
        PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/j2ee/dtds/web-app_2_3.dtd">
    
    <web-app>
      <display-name>sessionTest-jaxrpc</display-name>
      <description>A web application containing a simple JAX-RPC endpoint</description>
      <servlet>
        <servlet-name>SessionTestServletImpl</servlet-name>
        <servlet-class>com.hellking.study.webservice.session.SessionTestImpl</servlet-class>
        <load-on-startup>0</load-on-startup>
      </servlet>
      <servlet-mapping>
        <servlet-name>SessionTestServletImpl</servlet-name>
        <url-pattern>/mysessionTest</url-pattern>
      </servlet-mapping>
      <session-config>
        <session-timeout>60</session-timeout>
      </session-config>
    </web-app>
    
    												
    										

    可以看出,SessionTestServletImpl是作為Servlet運行的。為了在JAX-RPC環境啟動時就實例化這個服務端點,需要設置<load-on-startup>元素值為0。另外,<session-config>元素值指定了客戶端和JAX-RPC運行環境之間會話保持的時間。

    關于打包和部署方法在這里就不贅述了,您可以參考本系列文章《使用EJB2.1無狀態會話Bean作為Web服務端點》一文。





    回頁首


    開發客戶端


    我們知道,JAX-RPC有三種不同類型的客戶端:

    • 基于Stub;
    • 動態代理;
    • 動態調用。

    下面討論怎么在基于Stub和基于動態調用的客戶端使用Web服務會話。

    基于Stub的客戶端

    我們不得不從Stub接口的SESSION_MAINTAIN_PROPERTY屬性說起,如果在客戶端設置這個屬性為Boolean.TRUE,那么在Web服務交互過程中,服務端將維持一個HTTP會話,否則不會維持HTTP會話。基于Stub的客戶端代碼如例程7所示。

    												
    														例程7 基于Stub的客戶端
    package com.hellking.study.webservice.session;
    import javax.xml.rpc.Stub;
    /**
     *Web服務調用客戶端,測試Web服務會話。基于Stub的調用。
     */
    public class SessionTestClientUseStub
    {
      
       Stub stub;
       SessionTestIF sessionTest;
       //初始化Stub。
       public SessionTestClientUseStub()
       {
           stub = (Stub)(new MySessionTestService_Impl().getSessionTestIFPort());
           stub._setProperty(javax.xml.rpc.Stub.ENDPOINT_ADDRESS_PROPERTY, 
                    "http://127.0.0.1:8080/sessionTest/mysessionTest"); 
           stub._setProperty(Stub.SESSION_MAINTAIN_PROPERTY,Boolean.TRUE);
           sessionTest = (SessionTestIF)stub;
        }
       
        public static void main(String[] args) 
        {
      SessionTestClientUseStub test=new SessionTestClientUseStub();
      test.login();
      test.getLoginCount();
      test.logout();
            
        }
        /**
         *登錄。
         */
        public void login()
        {
         try {           
                 System.out.println("正在登錄...");
                System.out.println(sessionTest.login("userid-001","abc"));
            } catch (Exception ex) {
                ex.printStackTrace();
            }  
       }
       /**
        *獲得logincount屬性值。
        */
       public void getLoginCount()
       {
          try
          {
           System.out.println("LoginCount的值為:");
           System.out.println(sessionTest.getLoginCount());
          }
          catch (Exception ex) {
                ex.printStackTrace();
            }
        }
        /**
         *注銷。
         */
        public void logout()
        {
       …//省略代碼
        }
    }
    
    												
    										

    在服務調用之前,通過:

    												
    														stub._setProperty(Stub.SESSION_MAINTAIN_PROPERTY,Boolean.TRUE);
    
    												
    										

    方法來設置SESSION_MAINTAIN_PROPERTY屬性。

    部署好服務端后,運行這個代碼將獲得如圖1所示的結果。



    圖1 使用會話的測試結果

    可以看出,上面的調用達到了預期的效果。因為在getLoginCount方法中并沒有傳入任何參數,但獲得了前一個方法中登錄id的LoginCount值。

    如果我們屏蔽:

    												
    														stub._setProperty(Stub.SESSION_MAINTAIN_PROPERTY,Boolean.TRUE);
    
    												
    										

    代碼,或者更改為以下代碼:

    												
    														stub._setProperty(Stub.SESSION_MAINTAIN_PROPERTY,Boolean.FALSE);
    
    												
    										

    編譯后再運行這個客戶端,將獲得如圖2所示的結果。



    圖2 不使用會話的測試結果

    可以看出,這里返回的LoginCount為null,說明客戶端的會話并沒有保持。

    基于動態調用客戶端

    基于動態調用客戶端主要是通過javax.xml.rpc.Call接口來進行的。和基于Stub的客戶端一樣,也必須先設置一個屬性(Call.SESSION_MAINTAIN_PROPERTY)為Boolean.TRUE時才能使用HTTP會話。

    我們看這個客戶端的部分代碼,如例程8所示。

    												
    														例程8 基于Call的客戶端
    package com.hellking.study.webservice.session;
    
    … // imports
    /**
     *測試Web服務會話的使用
     */
    public class SessionTestClient {
        //一些調用參數。
        private static String qnameService = "MySessionTestService";
        private static String qnamePort = "SessionTestIFPort";
    
        private static String BODY_NAMESPACE_VALUE =   "urn:SessionTest";
        private static String ENCODING_STYLE_PROPERTY =
             "javax.xml.rpc.encodingstyle.namespace.uri"; 
        private static String NS_XSD =   "http://www.w3.org/2001/XMLSchema";
        private static String URI_ENCODING =  "http://schemas.xmlsoap.org/soap/encoding/";
             
        ServiceFactory factory;
        Service service; 
        Call call;
        QName port;
       //初始化。 
       public SessionTestClient()
       {
            try
            {
              factory = ServiceFactory.newInstance();
              service =  factory.createService(new QName(qnameService));
              port = new QName(qnamePort);
              call = service.createCall(port);
              call.setTargetEndpointAddress("http://127.0.0.1:8000/sessionTest/mysessionTest");
              …//省略部分代碼
        }
        catch(Exception e)
        {
         e.printStackTrace();
        }       
        
       }
       /**
        *測試login操作。
        */
       
       public void login()
        {
         try {
           
                QName QNAME_TYPE_STRING = new QName(NS_XSD, "string");
                call.setReturnType(QNAME_TYPE_STRING);
                call.setProperty(Call.SESSION_MAINTAIN_PROPERTY,Boolean.TRUE);
                call.setOperationName(new QName(BODY_NAMESPACE_VALUE,"login"));
                call.addParameter("String_1", QNAME_TYPE_STRING, 
                    ParameterMode.IN);
                call.addParameter("String_2", QNAME_TYPE_STRING, 
                    ParameterMode.IN);    
                String[] params = {new String("userid-001"),new String("abc")};
                String result = (String)call.invoke(params);
                System.out.println("正在登錄...");
                System.out.println(result);
    
            } catch (Exception ex) {
                ex.printStackTrace();
           }
        }
        /**
         *和Web服務交互,獲得LoginCount值。
         */
        public void getLoginCount()
        {
         try {
          
                call.removeAllParameters();         
                …//省略部分代碼
                 call.setProperty(Call.SESSION_MAINTAIN_PROPERTY,Boolean.TRUE);
                 …//省略部分代碼
                String result = (String)call.invoke(params);
                System.out.println(result);
    
            } catch (Exception ex) {
                ex.printStackTrace();
           }
         
        }
        /**
         *和Web服務交互,注銷操作
         */
        public void logout()
        {
           try {
                call.removeAllParameters();    
                  call.setProperty(Call.SESSION_MAINTAIN_PROPERTY,Boolean.TRUE);
                …//省略部分代碼
           }
        public static void main(String[] args) {
         SessionTestClient test=new SessionTestClient();
         test.login();
         test.getLoginCount();
         test.logout();
        }       
    }
    
    												
    										

    它的運行結果如圖3所示。



    圖3 基于Call的調用

    可以看出,它同樣獲得了預期的結果。





    回頁首


    總結


    JAX-RPC以HTTP作為傳輸協議,那么會話的保持可以從HTTP應用入手。JAX-RPC兩種服務端點中,只有基于Servlet的端點才能直接使用HTTP會話。要想在服務端點中訪問HTTP會話,Web服務實現類必須實現javax.xml.rpc.server.ServiceLifecycle接口,實現了這個接口的服務端點的生命周期由JAX-RPC運行環境來管理。

    通過ServletEndpointContext接口的getHttpSession來獲得客戶端的會話,這個會話由JAX-RPC運行環境維護。如果要在客戶端使用HTTP會話,那么不論是Stub還是Call都必須設置SESSION_MAINTAIN_PROPERTY屬性值為Boolean.TRUE。


    來源:http://www-128.ibm.com/developerworks/cn/webservices/ws-session/index.html

    posted on 2006-07-06 15:34 渠上月 閱讀(766) 評論(0)  編輯  收藏 所屬分類: java tips
    主站蜘蛛池模板: 久久亚洲国产午夜精品理论片| 成人浮力影院免费看| 成人免费毛片视频| 亚洲av专区无码观看精品天堂| 久久99国产乱子伦精品免费| 亚洲人成色7777在线观看| 97在线视频免费公开视频| 国产成人亚洲精品91专区手机| 日韩免费在线中文字幕| 国产成人99久久亚洲综合精品 | 国外亚洲成AV人片在线观看| 国产亚洲福利一区二区免费看| 免费毛片网站在线观看| 美女扒开尿口给男人爽免费视频 | 日韩毛片免费在线观看| 久久亚洲国产成人影院| 四虎成人免费影院网址| 亚洲国产av玩弄放荡人妇 | 亚洲一区二区三区AV无码| 久久九九免费高清视频| 亚洲av无码专区在线播放| 在线人成精品免费视频| 亚洲午夜在线一区| 日韩一级免费视频| 一个人看的免费观看日本视频www| 亚洲欧洲日产国码av系列天堂 | 啦啦啦中文在线观看电视剧免费版 | 亚洲无线码一区二区三区| 成在人线av无码免费高潮喷水| 亚洲视频精品在线| 成人无码区免费视频观看| 黄色a级免费网站| 亚洲香蕉成人AV网站在线观看| 日韩免费人妻AV无码专区蜜桃| va天堂va亚洲va影视中文字幕| 超pen个人视频国产免费观看 | 狠狠色香婷婷久久亚洲精品| 国产成人免费高清在线观看| 99麻豆久久久国产精品免费 | 美女露100%胸无遮挡免费观看| 亚洲人成网77777色在线播放|