顯示具有 WebService 標籤的文章。 顯示所有文章
顯示具有 WebService 標籤的文章。 顯示所有文章

2017年2月3日 星期五

前端呼叫WebService、同源策略 ( WebService 五)

還是使用上一篇 Spring 架好的CXF
再新增一個 動態Web 專案


※使用JS呼叫

可看W3C官網複製

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>Insert title here</title>
        <script type="text/javascript">
            function jsCallWebService() {
                const a = document.getElementById("account").value;
                const p = document.getElementById("password").value;
                const n1 = document.getElementById("num1").value;
                const n2 = document.getElementById("num2").value;
                var data = getData(a, p, n1, n2);
        
                const request = getXmlHttp();
                request.onreadystatechange = function() {
                    if (request.readyState == 4 && request.status == 200) {
                        var result = request.responseXML;
                        var rtn = result.getElementsByTagName("return")[0];
                        var vue = rtn.firstChild.data;
                        document.getElementById("ta").value = vue;
                    }
                };
    
                request.open("POST", "http://localhost:8080/SCXF/aaa/bbb/ccc", true);
                request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
                request.send(data);
            }
    
            function getXmlHttp() {
                var xmlhttp;
                if (window.XMLHttpRequest) {
                    // code for modern browsers
                    xmlhttp = new XMLHttpRequest();
                } else {
                    // code for old IE browsers
                    xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
                }
                return xmlhttp;
            }
    
            function getData(a, p, n1, n2){
                var d =
                    '<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:test="http://test.hello/">' +
                        '<soapenv:Header>' +
                            '<login>' +
                                '<acc>' + a + '</acc>' +
                                '<pass>' + p + '</pass>' +
                            '</login>' +
                        '</soapenv:Header>' +
                        '<soapenv:Body>' +
                            '<test:add>' +
                                '<arg0>' + n1 + '</arg0>' +
                                '<arg1>' + n2 + '</arg1>' +
                            '</test:add>' +
                        '</soapenv:Body>' +
                    '</soapenv:Envelope>';
                return d;
            }
        </script>
    </head>
    
    <body>
        帳號: <input id="account" type="text" value="bruce" /><br />
        密碼: <input id="password" type="password" value="12345" /><br />
        參數1:<input id="num1" type="number" placeholder=0 /><br />
        參數2:<input id="num2" type="number" placeholder=0 /><br />
        <textarea id="ta"></textarea><br />
        <button onclick="jsCallWebService()">javascript AJAX call WebService</button>
    </body>
</html>

※getData方法我本來沒有提出來,run也是ok的,但提出來後出了「org.apache.cxf.binding.soap.SoapFault: Error reading XMLStreamReader: Unexpected character 'u' (code 117) in prolog; expected '<'」的錯
我試著將要傳的參數、帳密都寫死,然後不用「+」串接,也不換行,但還是出一樣的錯
最後我用一個變數來接,最後在return,就沒出錯了



※使用jQuery呼叫

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>Insert title here</title>
        <script src="https://code.jquery.com/jquery-1.10.2.js"></script>
        <script type="text/javascript">
            function jsCallWebService() {
                const a = document.getElementById("account").value;
                const p = document.getElementById("password").value;
                const n1 = document.getElementById("num1").value;
                const n2 = document.getElementById("num2").value;
                var data = getData(a, p, n1, n2);
                        
                $.post(
                    "http://localhost:8080/SCXF/aaa/bbb/ccc", 
                    data,
                    function(result) {
                        var rtn = result.getElementsByTagName("return")[0];
                        var vue = rtn.firstChild.data;
                        document.getElementById("ta").value = vue;
                    }
                );
    
                //     $.ajax({
                //         type: "POST",
                //         url: "http://localhost:8080/SCXF/aaa/bbb/ccc",
                //         data: data,
                //         success: function(result){
                //             var rtn = result.getElementsByTagName("return")[0];
                //             var vue = rtn.firstChild.data;
                //             document.getElementById("ta").value = vue;
                //         }
                //     });
            }
            
            function getData(a, p, n1, n2){
                var d =
                    '<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:test="http://test.hello/">' +
                        '<soapenv:Header>' +
                            '<login>' +
                                '<acc>' + a + '</acc>' +
                                '<pass>' + p + '</pass>' +
                            '</login>' +
                        '</soapenv:Header>' +
                        '<soapenv:Body>' +
                            '<test:add>' +
                                '<arg0>' + n1 + '</arg0>' +
                                '<arg1>' + n2 + '</arg1>' +
                            '</test:add>' +
                        '</soapenv:Body>' +
                    '</soapenv:Envelope>';
                return d;
            }
        </script>
    </head>
    <body>
        帳號: <input id="account" type="text" value="bruce" /><br />
        密碼:<input id="password" type="password" value="12345" /><br />
        參數1:<input id="num1" type="number" placeholder=0 /><br />
        參數2:<input id="num2" type="number" placeholder=0 /><br />
        <textarea id="ta"></textarea><br />
        <button onclick="jsCallWebService()">javascript AJAX call WebService</button>
    </body>
</html>

※註解是比較麻煩的寫法



※同源策略

localhost:8080改成「IP:8080」雖然不會出錯,但回傳就不會將值取出來了,用JS和jQuery都一樣

.什麼是同源策略可參考Mozilla 官網,網址列可將「en-US」改成「zh-TW」或「zh-CN」
總之就是http://www.google.com:80 分成三個部分
1.「http://」是protocol,還有ftp、https…等
2.「www.google.com」 是域名
3.「:80」是port,沒有寫就是:80
只要以上三個部分一樣,就是同源
現在大部分的瀏覽器預設都將 localhost 和轉換過的 IP 認為是不同的

.同源策略又衍生了 JSONP、CORS 技術
也就是同源策略不包括一些特定的標籤、如<script>標籤…等,其他的標籤上一個Mozilla 連結有寫
JSONP只能用在GET請求;CORS沒這個問題,但要較新的瀏覽器才有支援

.瀏覽器可關閉同源策略,可參考這個網址
我改IE 11是有成功的,但Chrome沒成功
使用chrome指令時先切換到chrome安裝資料夾,如我的安裝在「C:\Program Files (x86)\Google\Chrome\Application」,裡面有一支執行檔叫 chrome.exe ,切到這裡下指令即可
不然就要設環境變數了



※用HttpURLConnection可使localhost和IP一樣

package servlet;
    
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
    
@WebServlet("/conn")
public class Connection extends HttpServlet {
    private static final long serialVersionUID = 1L;
    
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String a = request.getParameter("account");
        String p = request.getParameter("password");
        String n1 = request.getParameter("number1");
        String n2 = request.getParameter("number2");
        System.out.println(a + ":" + p + ":" + n1 + ":" + n2);
    
        String data = 
            "<soapenv:Envelope xmlns:soapenv='http://schemas.xmlsoap.org/soap/envelope/' xmlns:test='http://test.hello/'>" +
                "<soapenv:Header>" + 
                    "<login>" + 
                        "<acc>" + a + "</acc>" + 
                        "<pass>" + p + "</pass>" + 
                    "</login>"
                + "</soapenv:Header>" + 
                "<soapenv:Body>" + 
                    "<test:add>" + 
                        "<arg0>" + n1 + "</arg0>" + 
                        "<arg1>" + n2 + "</arg1>" + 
                    "</test:add>" + 
                "</soapenv:Body>" + 
            "</soapenv:Envelope>";
    
        URL url = new URL("http://192.168.0.10:8080/SCXF/aaa/bbb/ccc");
    
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod("POST");// 預設是GET
        connection.setDoOutput(true);// 往server端寫資料,預設是false
        connection.setDoInput(true);// 接收server端的資料,預設是true,所以可不寫
        connection.setRequestProperty("Content-Type", "text/xml;charset=utf-8");
    
        // 向server端寫資料
        OutputStream out = connection.getOutputStream();
        out.write(data.getBytes("UTF-8"));
        out.close();
    
        int responseCode = connection.getResponseCode();
        if (responseCode == 200) {
            // 接收server端回傳的資料
            InputStream in = connection.getInputStream();
            System.out.println("in長度: " + in.available());
    
            // 將server端的資料給瀏覽器
            // 如給text/xml,那jQuery的dataType也必須是xml;給text/text,那jQuery的dataType也必須是text
            response.setContentType("text/xml;charset=UTF-8");
            ServletOutputStream sout = response.getOutputStream();
    
            byte[] buffer = new byte[1024];
            int len = 0;
            while ((len = in.read(buffer)) > 0) {
                sout.write(buffer, 0, len);
            }
            sout.flush();
            in.close();
            sout.close();
        }
    }
}


※前端

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>Insert title here</title>
        <script src="https://code.jquery.com/jquery-1.10.2.js"></script>
        <script type="text/javascript">
            function jsCallWebService() {
                $.post(
                    "conn",
                    {
                        account : $('#account').val(),
                        password : $('#password').val(),
                        number1 : $('#num1').val(),
                        number2 : $('#num2').val()
                    },
                    function(result) {
                        // var vue = $(result).find('return').text();
                        var rtn = result.getElementsByTagName("return")[0];
                        var vue = rtn.firstChild.data;
                        document.getElementById("ta").value = vue;
                    },
                    'xml'
                );
    
                //     $.ajax({
                //         type: "POST",
                //         url: "conn",
                //         data: {
                //             account : $('#account').val(),
                //             password : $('#password').val(),
                //             number1 : $('#num1').val(),
                //             number2 : $('#num2').val()
                //         },
                //         success: function(result){
                //            // var vue = $(result).find('return').text();
                //             var rtn = result.getElementsByTagName("return")[0];
                //             var vue = rtn.firstChild.data;
                //             document.getElementById("ta").value = vue;
                //         },
                //         dataType:'xml'
                //     });
            }
        </script>
    </head>
    
    <body>
        帳號: <input id="account" type="text" value="bruce" /><br />
        密碼: <input id="password" type="password" value="12345" /><br />
        參數1:<input id="num1" type="number" placeholder=0 /><br />
        參數2:<input id="num2" type="number" placeholder=0 /><br />
        <textarea id="ta"></textarea><br />
        <button onclick="jsCallWebService()">javascript AJAX call WebService</button>
    </body>
</html>

※$(result).find('return').text();使用在dataType是text時

※如果不寫dataType,jQuery預設是智能判斷,會找以下這5種 xml, json, script, text, html

2017年1月26日 星期四

CXF 與 Spring 整合 ( WebService 四)

官網的maven 設定連結
官網的spring 設定連結

※pom.xml

<properties>
    <cxf.version>3.1.9</cxf.version>
    <spring.version>3.2.15.RELEASE</spring.version>
</properties>
    
<dependencies>
    <dependency>
        <groupId>org.apache.cxf</groupId>
        <artifactId>cxf-rt-frontend-jaxws</artifactId>
        <version>${cxf.version}</version>
    </dependency>
    
    <dependency>
        <groupId>org.apache.cxf</groupId>
        <artifactId>cxf-rt-transports-http</artifactId>
        <version>${cxf.version}</version>
    </dependency>
    
    <dependency>
        <groupId>org.apache.cxf</groupId>
        <artifactId>cxf-rt-transports-http-jetty</artifactId>
        <version>${cxf.version}</version>
    </dependency>
    
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
    </dependency>
    
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>${spring.version}</version>
    </dependency>
    
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
        <version>${spring.version}</version>
    </dependency>
</dependencies>

※Spring基本要兩個包,core和context,但因為要run server,所以還要加web包



※伺服器端

要整合Spring,伺服器端要Web專案才可以


※web.xml

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>WEB-INF/applicationContext.xml</param-value>
</context-param>
    
<listener>
    <listener-class>
        org.springframework.web.context.ContextLoaderListener
    </listener-class>
</listener>
    
<servlet>
    <servlet-name>cxf</servlet-name>
    <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>
    
<servlet-mapping>
    <servlet-name>cxf</servlet-name>
    <url-pattern>/*</url-pattern>
</servlet-mapping>

※<servlet-class>的路徑在Servlet Transport能找到

※<servlet-class>的CXFServlet在cxf-rt-transports-http-x.x.x.jar裡面



※applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:jaxws="http://cxf.apache.org/jaxws"
    xsi:schemaLocation=" 
    http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans.xsd 
    http://cxf.apache.org/jaxws 
    http://cxf.apache.org/schemas/jaxws.xsd">
    
    <import resource="classpath:META-INF/cxf/cxf.xml" />
    <import resource="classpath:META-INF/cxf/cxf-servlet.xml" />
    
    <!-- <bean id="hs" class="hello.test.HelloWS" /> -->
    <!-- <jaxws:endpoint implementor="#hs" address="/aaa/bbb/ccc" /> -->
    
    <jaxws:endpoint implementor="hello.test.HelloWS" address="/aaa/bbb/ccc" />
</beans>

※註解的部分是第二種方法

※address是要接在IP後的

※SEI和之前一樣

※cxf.xml在cxf-core-x.x.xjar裡面的「META-INF\cxf」

※cxf-servlet.xml在cxf-rt-transports-http-x.x.x.jar裡面的「META-INF\cxf」

※如果有cxf-extension-soap.xml,可在cxf-rt-bindings-soap-2.2.3.jar 裡面的「META-INF\cxf」找到

※執行後會自動run出如下的畫面

※所以整合後,原本的網址會變成:IP加上專案名稱

※wsdl如下:

※aaa/bbb/ccc 是在 applicationContext.xml 裡設定的

※完成後就可用類似 SoapUI的軟體測試



※用戶端

用一般的專案即可,不用用到web專案,當然要用也行
用wsimport 或 wsdl2java 產生用戶端程式碼後,裡面只有一隻interface


※applicationContext.xml

    
<beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:jaxws="http://cxf.apache.org/jaxws"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="
        http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://cxf.apache.org/jaxws 
        http://cxf.apache.org/schemas/jaxws.xsd">    
    
    <jaxws:client id="hws" 
        serviceClass="hello.test.IHelloWS"
        address="http://localhost:8080/SCXF/aaa/bbb/ccc" />
        
    <!--
        <bean id="hws" factory-bean="clientFactory" factory-method="create"/>
        <bean id="clientFactory" class="org.apache.cxf.jaxws.JaxWsProxyFactoryBean">
            <property name="serviceClass" value="hello.test.IHelloWS"/>
            <property name="address" value="http://localhost:8080/SCXF/aaa/bbb/ccc"/>
        </bean>
    -->
</beans>

※註解的部分是另外一種方法,看來CXF已幫我們寫好了,所以不一定要用jaxws的命名空間


※測試類

ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
IHelloWS client = (IHelloWS) context.getBean("hws");
System.out.println(client.add(50, 20));
context.close();



※攔截器


伺服器端和用戶端的java檔和上一篇一樣


※用戶端的applicationContext.xml

<jaxws:client id="hws" 
    serviceClass="hello.test.IHelloWS"
    address="http://localhost:8080/SCXF/aaa/bbb/ccc">
    
    <jaxws:outInterceptors>
        <bean class="AddLoginInfo">
            <constructor-arg name="account" value="bruce" />
            <constructor-arg name="password" value="12345" />
        </bean>
    </jaxws:outInterceptors>
</jaxws:client>

※


※伺服器端的applicationContext.xml

<jaxws:endpoint implementor="hello.test.HelloWS" address="/aaa/bbb/ccc">
    <jaxws:inInterceptors>
        <bean class="hello.test.CheckLoginInfo" />
    </jaxws:inInterceptors>
</jaxws:endpoint>

※


2017年1月22日 星期日

CXF 攔截器 ( WebService 三)

JDK沒有攔截器的功能,但CXF有



※內鍵攔截器

以logging為例,如下:


※伺服器端

import java.util.List;
import javax.xml.ws.Endpoint;
import org.apache.cxf.interceptor.Interceptor;
import org.apache.cxf.interceptor.LoggingInInterceptor;
import org.apache.cxf.interceptor.LoggingOutInterceptor;
import org.apache.cxf.jaxws.EndpointImpl;
import org.apache.cxf.message.Message;
    
String url = "http://localhost:8888/aaa/bbb/ccc";
EndpointImpl publish = (EndpointImpl) Endpoint.publish(url, new HelloWS());
    
System.out.println("增加server in log");
List<Interceptor<? extends Message>> in = publish.getInInterceptors();
in.add(new LoggingInInterceptor());
    
System.out.println("增加server out log");
List<Interceptor<? extends Message>> out = publish.getOutInterceptors();
out.add(new LoggingOutInterceptor());
    
System.out.println("server is start!");

※注意import的包,CXF有很多同名的class,但package不同

※在網址打上xxx.wsdl就會執行伺服器端in的部分,在console會顯示

※out的部分可用第一篇的JavaClientCallWebService.java


※用戶端

import java.util.List;
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.frontend.ClientProxy;
import org.apache.cxf.interceptor.Interceptor;
import org.apache.cxf.interceptor.LoggingInInterceptor;
import org.apache.cxf.interceptor.LoggingOutInterceptor;
import org.apache.cxf.message.Message;
    
HelloWSService hws = new HelloWSService();
IHelloWS hw = hws.getHelloWSPort();
    
Client client = ClientProxy.getClient(hw);
System.out.println("增加client out log");
List<Interceptor<? extends Message>> out = client.getOutInterceptors();
out.add(new LoggingOutInterceptor());
    
System.out.println("增加client in log");
List<Interceptor<? extends Message>> in = client.getInInterceptors();
in.add(new LoggingInInterceptor());
    
System.out.println(hw.add(1, 2));

※使用wsimport或wsdl2java產生程式碼後,使用如上的程式碼,這前幾篇也有做過,只是多個中間攔截器的部分

※首先先向server端丟出訊息,因為現在寫的是用戶端,所以對用戶端來說是out,然後伺服器端的in會接收,在來是伺服器端的out,最後再丟回給用戶端的out,攔截器完成後,再印出程式碼



※自定攔截器


※用戶端

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
    <soap:Body>
        <ns2:add xmlns:ns2="http://test.hello/">
            <arg0>1</arg0>
            <arg1>2</arg1>
        </ns2:add>
    </soap:Body>
</soap:Envelope>

※內鍵攔截器的伺服器端的console有Inbound Message,裡面的Payload就是長這個樣子


※AddLoginInfo.java 

import java.util.List;
import javax.xml.namespace.QName;
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.headers.Header;
import org.apache.cxf.helpers.DOMUtils;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
    
public class AddLoginInfo extends AbstractPhaseInterceptor<SoapMessage> {
    private String account;
    private String password;
    
    public AddLoginInfo(String account, String password) {
        super(Phase.PRE_PROTOCOL); // 表示在轉換成SOAP之前
        this.account = account;
        this.password = password;
    }
    
    @Override
    public void handleMessage(SoapMessage msg) throws Fault {
        /**
         * <login> <acc></acc> <pass></pass> </login>
         */
        Document doc = DOMUtils.createDocument();
        Element loginEle = doc.createElement("login");
    
        Element accEle = doc.createElement("acc");
        accEle.setTextContent(account);
    
        Element passEle = doc.createElement("pass");
        passEle.setTextContent(password);
    
        loginEle.appendChild(accEle);
        loginEle.appendChild(passEle);
    
        List<Header> headers = msg.getHeaders();
        headers.add(new Header(new QName("login"), loginEle));
    
        System.out.println("客戶端的 handleMessage執行成功!");
    }
}

※繼承 AbstractPhaseInterceptor 並增加 SOAP 的 head

※QName裡的字串必需與createElement裡的字串一樣,表示要攔截login標籤,否則攔截不到


※測試

HelloWSService hws = new HelloWSService();
IHelloWS hw = hws.getHelloWSPort();
    
Client client = ClientProxy.getClient(hw);
System.out.println("增加client out log");
List<Interceptor<? extends Message>> out = client.getOutInterceptors();
out.add(new AddLoginInfo("bruce", "12345"));
    
System.out.println(hw.add(1, 2));

※將out的攔截器指向新增的AddLoginInfo


※伺服器端的console

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
    <soap:Header>
        <login>
            <acc>bruce</acc>
            <pass>12345</pass>
        </login>
    </soap:Header>
    <soap:Body>
        <ns2:add xmlns:ns2="http://test.hello/">
            <arg0>1</arg0>
            <arg1>2</arg1>
        </ns2:add>
    </soap:Body>
</soap:Envelope>

※一樣是 Inbound Message 的 Payload,此時會印出新增的login


※伺服器端

import javax.xml.namespace.QName;
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.headers.Header;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
    
public class CheckLoginInfo extends AbstractPhaseInterceptor<SoapMessage> {
    public CheckLoginInfo() {
        super(Phase.PRE_PROTOCOL);
    }
    
    @Override
    public void handleMessage(SoapMessage msg) throws Fault {
        Header header = msg.getHeader(new QName("login"));
        if (header != null) {
            Element ele = (Element) header.getObject();
    
            NodeList acc = ele.getElementsByTagName("acc");
            String account = acc.item(0).getTextContent();
    
            NodeList pass = ele.getElementsByTagName("pass");
            String password = pass.item(0).getTextContent();
    
            if ("bruce".equals(account) && "12345".equals(password)) {
                System.out.println("驗證通過!");
                return;
            }
        }
    
        System.out.println("驗證未通過!");
        throw new Fault(new RuntimeException("帳號和密碼不正確"));
    }
}

※同樣繼承 AbstractPhaseInterceptor 並從head取出帳號和密碼


※server.java

String url = "http://localhost:8888/aaa/bbb/ccc";
EndpointImpl publish = (EndpointImpl) Endpoint.publish(url, new HelloWS());
    
List<Interceptor<? extends Message>> in = publish.getInInterceptors();
in.add(new CheckLoginInfo());
    
System.out.println("server is start!");

※因為客戶端丟過來,所以當然是用in去接

※此時輸入正確的帳密才會印出「call add method!」,否則就會被攔截器攔截並拋出例外

2017年1月15日 星期日

架設CXF、WSDL格式、SOAP格式 ( WebService 二)

※架設CXF

到apache官網下載CXF框架,下載ZIP的
然後將lib裡面的jar檔通通複製到專案裡
啟動server的程式碼和上一篇一樣,但Console不太一樣了

本來只有黑色的字,現在會發現有jetty和cxf的字

啟動server的Endpoint.publish的回傳值,用JDK是「com.sun.xml.internal.ws.transport.http.server.EndpointImpl」,但用了CXF後會變成「org.apache.cxf.jaxws.EndpointImpl」

JDK是不能回傳Map的,我用java8都還是不行;在啟動伺服器端的程式時,會報「GetXXXResponse do not have a property of the name return」,但用CXF是可以的,但此時用wsimport生成的程式碼,回傳的不是Map,寫test時用法如下:
※
HelloWSService hws = new HelloWSService();
IHelloWS hw = hws.getHelloWSPort();
    
Return map = hw.getMap();
List<Entry> entry = map.getEntry(); // map.entry
for (Entry e : entry) {
    System.out.println(e.getKey());
    System.out.println(e.getValue());
}

※map.entry和map.getEntry()都可以,但map.entry是protected;map.getEntry()是public,


在下載下來的資料夾裡,有個bin資料夾,裡面都是一些轉換的工具,這裡使用wsdl2java,當然要先在環境變數裡設定,用法和JDK的差不多,雖然用法可以用wsdl2java -h,但還可以看官網的表格比較清楚

此時下wsdl2java -d D:\xxx http://localhost:8888/aaa/bbb/ccc?wsdl,不用加-keep就有java檔了,其他測試方法和上一篇一樣,如果不想加-d就先切換到想生成的路徑

但此時還有一個地方不太一樣,就是wsdl的格式稍有不同
<wsdl:definitions xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:tns="http://test.hello/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:ns1="http://schemas.xmlsoap.org/soap/http" name="HelloWSService" targetNamespace="http://test.hello/">
    <wsdl:types>
        <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://test.hello/" elementFormDefault="unqualified" targetNamespace="http://test.hello/" version="1.0">
            <xs:element name="add" type="tns:add"/>
            <xs:element name="addResponse" type="tns:addResponse"/>
            <xs:element name="hello" type="tns:hello"/>
            <xs:element name="helloResponse" type="tns:helloResponse"/>
            <xs:complexType name="hello">
                <xs:sequence/>
            </xs:complexType>
            <xs:complexType name="helloResponse">
                <xs:sequence/>
            </xs:complexType>
            <xs:complexType name="add">
                <xs:sequence>
                    <xs:element minOccurs="0" name="arg0" type="xs:int"/>
                    <xs:element name="arg1" type="xs:int"/>
                </xs:sequence>
            </xs:complexType>
            <xs:complexType name="addResponse">
                <xs:sequence>
                    <xs:element minOccurs="0" name="return" type="xs:string"/>
                </xs:sequence>
            </xs:complexType>
        </xs:schema>
    </wsdl:types>
    <wsdl:message name="hello">
        <wsdl:part element="tns:hello" name="parameters"></wsdl:part>
    </wsdl:message>
    <wsdl:message name="helloResponse">
        <wsdl:part element="tns:helloResponse" name="parameters"></wsdl:part>
    </wsdl:message>
        <wsdl:message name="addResponse">
        <wsdl:part element="tns:addResponse" name="parameters"></wsdl:part>
    </wsdl:message>
    <wsdl:message name="add">
        <wsdl:part element="tns:add" name="parameters"></wsdl:part>
    </wsdl:message>
    <wsdl:portType name="IHelloWS">
        <wsdl:operation name="hello">
            <wsdl:input message="tns:hello" name="hello"></wsdl:input>
            <wsdl:output message="tns:helloResponse" name="helloResponse"></wsdl:output>
        </wsdl:operation>
        <wsdl:operation name="add">
            <wsdl:input message="tns:add" name="add"></wsdl:input>
            <wsdl:output message="tns:addResponse" name="addResponse"></wsdl:output>
        </wsdl:operation>
    </wsdl:portType>
    <wsdl:binding name="HelloWSServiceSoapBinding" type="tns:IHelloWS">
        <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
        <wsdl:operation name="hello">
            <soap:operation soapAction="" style="document"/>
            <wsdl:input name="hello">
                <soap:body use="literal"/>
            </wsdl:input>
            <wsdl:output name="helloResponse">
                <soap:body use="literal"/>
            </wsdl:output>
        </wsdl:operation>
        <wsdl:operation name="add">
            <soap:operation soapAction="" style="document"/>
            <wsdl:input name="add">
                <soap:body use="literal"/>
            </wsdl:input>
            <wsdl:output name="addResponse">
                <soap:body use="literal"/>
            </wsdl:output>
        </wsdl:operation>
    </wsdl:binding>
    <wsdl:service name="HelloWSService">
        <wsdl:port binding="tns:HelloWSServiceSoapBinding" name="HelloWSPort">
            <soap:address location="http://localhost:8888/aaa/bbb/ccc"/>
        </wsdl:port>
    </wsdl:service>
</wsdl:definitions>

※types下的schema本來是import的,現在直接寫出來了



※WSDL格式

<definitions>
    <types>
        <xsd:schema>
            <xsd:import />(裡面是<element>)
        </xsd:schema>
    </types>
    <message>
        <part />
    </message>
    <message>
        <part />
    </message>
    <portType>
        <operation>
            <input />
            <output />
        </operation>
    </portType>
    <binding>
        <soap:binding />
        <operation>
            <soap:operation />
            <input>
                <soap:body />
            </input>
            <output>
                <soap:body />
            </output>
        </operation>
    </binding>
    <service>
        <port>
            <soap:address />
        </port>
    </service>
</definitions>

※必須有XML schema 基礎

※types:定義XML約束,方法的名稱、參數、回傳值
message:一個方法會有兩個,會對應到types裡的約束
portType:介面類型
binding:綁定介面的實作
service:一組服務

※從service開始由下往上,一層一層解析

※types > schema > element
message > part          part連到element
portType > operation > input/ouput          input/output連到message
binding > operation > input/output          bingding連到portType
service > port > addrress          port連到binding
由service的name屬性開始解析



※SOAP格式

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:test="http://test.hello/">
    <soapenv:Header/>
    <soapenv:Body>
        <test:add>
            <arg0></arg0>
            <arg1></arg1>
        </test:add>
    </soapenv:Body>
</soapenv:Envelope>
--------------------
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
    <soap:Body>
        <ns2:addResponse xmlns:ns2="http://test.hello/">
            <return>Answer is 100</return>
        </ns2:addResponse>
    </soap:Body>
</soap:Envelope>

※從WSDL解析出來的XML,可以讓java做轉換

※上面是Request;下面是Response

※基本上很像HTML,HTML下有HEAD和BODY;SOAP是ENVELOPE下有HEADER和BODY,BODY裡有請求的方法或回傳值


2016年10月30日 星期日

WebService 基礎 ( WebService 一)

.WSDL:web service definition language
定義了web service的server端和client端請求和回應的格式,主要有5個,分別是types、message、portType、binding、service
一個web service對應唯一的wsdl文件

.SOAP:simple object access protocal
簡單的、基于HTTP和XML的、用在WEB上交換資料的
soap訊息:請求/回應訊息
http+xml片斷

.SEI:WebService EndPoint Interface
WebService server端用來處理請求的介面,java就是寫一個介面,上面加上@WebService

.CXF:Celtix + XFire
XFire原本就是webservice框架,可能有一些問題,修改bug後,加上Celtix變成新的框架,是apache開發的,用來開發webservice的server端和client端



※SEI和實作

package hello.test;
import javax.jws.WebService;
    
@WebService
public interface IHelloWS {
    public void hello();
}
--------------------
package hello.test;
import javax.jws.WebService;
    
@WebService(endpointInterface = "hello.test.IHelloWS")
public class HelloWS implements IHelloWS {
    @Override
    public void hello() {
        System.out.println("Hello WebService!");
    }
    
    public String add(Integer a, int b) {
        System.out.println("call add method!");
        return "Answer is " + (a + b);
    }
}

※


※啟動伺服器

package hello.test;
import javax.xml.ws.Endpoint;
    
public class MyServer {
    public static void main(String[] args) {
        String url = "http://localhost:8888/aaa/bbb/ccc";
        Endpoint.publish(url, new HelloWS());
        System.out.println("server is start!");
    }
}

※如有修改(SEI、SEI實作或這一隻測試類),必須重啟才會生效

※複製url到瀏覽器,最後加上「?wsdl」會有如下的xml格式,也就是wsdl

<definitions xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:wsp="http://www.w3.org/ns/ws-policy" xmlns:wsp1_2="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:wsam="http://www.w3.org/2007/05/addressing/metadata" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://test.hello/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://schemas.xmlsoap.org/wsdl/" targetNamespace="http://test.hello/" name="HelloWSService">
    <types>
        <xsd:schema>
            <xsd:import namespace="http://test.hello/" schemaLocation="http://localhost:8888/aaa/bbb/ccc?xsd=1"/>
        </xsd:schema>
    </types>
    
    <message name="hello">
        <part name="parameters" element="tns:hello"/>
    </message>
    
    <message name="helloResponse">
        <part name="parameters" element="tns:helloResponse"/>
    </message>
    
    <portType name="IHelloWS">
        <operation name="hello">
            <input wsam:Action="http://test.hello/IHelloWS/helloRequest" message="tns:hello"/>
            <output wsam:Action="http://test.hello/IHelloWS/helloResponse" message="tns:helloResponse"/>
        </operation>
    </portType>
    
    <binding name="HelloWSPortBinding" type="tns:IHelloWS">
        <soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"/>
        <operation name="hello">
        
            <soap:operation soapAction=""/>
            
            <input>
                <soap:body use="literal"/>
            </input>
            
            <output>
                <soap:body use="literal"/>
            </output>
        </operation>
    </binding>
    
    <service name="HelloWSService">
        <port name="HelloWSPort" binding="tns:HelloWSPortBinding">
            <soap:address location="http://localhost:8888/aaa/bbb/ccc"/>
        </port>
    </service>
</definitions>

※types最裡層還有一個import的網址,再開一個瀏覽器分頁,會發現它長如下的樣子

<xs:schema xmlns:tns="http://test.hello/" xmlns:xs="http://www.w3.org/2001/XMLSchema" version="1.0" targetNamespace="http://test.hello/">
    <xs:element name="hello" type="tns:hello"/>
    
    <xs:element name="helloResponse" type="tns:helloResponse"/>
    
    <xs:complexType name="hello">
        <xs:sequence/>
    </xs:complexType>
    
    <xs:complexType name="helloResponse">
        <xs:sequence/>
    </xs:complexType>
</xs:schema>

※格式內容下一篇再說,先說明SEI的部分

※@WebService有6個屬性,都是接收String,分別是
name、targetNamespace、serviceName、portName、wsdlLocation、endpointInterface

其中EI(也就是interface)只有3個合法值:name、targetNamespace、wsdlLocation,API有特別註明「This member-value is not allowed on endpoint interfaces.」,
其中name和targetNamespace屬性,如果EI和實作類的設定不同,會以EI為主
wsdlLocation只能設在SEI裡,如果實作類有設,會在執行時出「[failed to localize] cannot.load.wsdl(path/xxx.wsdl)」的錯


.endpointInterface
上面有用到,沒加也是可以run,但客戶端請求時有所不同,下面會說明
有加只會將有實作的部分暴露出來
沒加會將自己的方法暴露出來

一個方法會對應
兩個message
兩個schema(在import裡)
一個portType-->operation
一個binding-->operation

.targetNamespace:,指定targetNamespace,不加為「http://從右到左的包名/」,此例為「http://test.hello/」

.serviceName:指定serviceName,不加為「實作類別名+Service」,此例為「HelloWSService」

.name:指定portType的name屬性,不加為「介面名」,此例為「IHelloWS」
還會影響portType-->operation-->input/output的wsam:Action屬性,這個屬性是「targetNamespace名稱/name名稱/方法名稱+Request/Response」,input為Request;output為Response
,此例input為「http://test.hello/HelloWS/addRequest」;output為「http://test.hello/HelloWS/addResponse」

.portName:指定service-->port的name屬性,不加為「實作類別名+Port」,此例為「HelloWSPort」;
還會影響binding的name屬性,為「port的name屬性+Binding」,此例為「HelloWSPortBinding」

.wsdlLocation:指定wsdl的路徑

※還有@WebResult、@WebParam、@WebMethod,這三個annotation也很重要
@WebResult寫在方法上,name屬性可以設定xml的回傳值的名稱,不打為「return」
@WebParam寫在參數裡,name屬性可以設定xml的參數名稱,不打為「arg+數字(從0開始)」
@WebMethod 寫在方法上,有三個值operationName、action、exclude
operationName屬性可以設定message的name屬性和portType-->operation的name屬性,不打為方法名
exclude不能加在EI,預設為false,若為true,此方法不會暴露出來

※在definitions有很多屬性,其中targetNamespace和name下面程式的QName會用到,分別對應到第一和第二個參數



※Eclipse 的 Web Services Explorer

其實Eclipse內鍵也有一個瀏覽的工具,也類似於在網址上打XXX~?wsdl,裡面可以對wsdl做request、response,如下畫面

1.首先先切換到Java EE視圖
2.按Launch the Web Services Explorer,或者Run-->Launch the Web Services Explorer也是一樣
3.右上角有個WSDL Page
4.將剛剛網址的wsdl貼上後按Go,每按一次Go,左邊的Navigator都會出現一個你打的網址,就算你打的網址一樣也會出現
想刪除,只要在左框(Navigator)的右上角Clear即可


※JavaClientCallWebService.java

package xxx.ooo;
import java.net.MalformedURLException;
import java.net.URL;
import javax.xml.namespace.QName;
import javax.xml.ws.Service;
    
public class JavaClientCallWebService {
    public static void main(String[] args) {
        Url url = null;
        try {
            url = new URL("http://localhost:8888/aaa/bbb/ccc?wsdl");
            QName qname = new QName("http://test.hello/", "HelloWSService");
            Service service = Service.create(url, qname);
            IHelloWS call = service.getPort(IHelloWS.class);
            call.hello();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
    }
}

※上面是用Eclipse內鍵的工具呼叫WebService,這裡是用寫程式的方式呼叫

※將wsdl裡面的definitions的targetNamespace和name放在QName的建構子裡即可

※如果實作類的@WebService沒有endpointInterface屬性,會報「javax.xml.ws.WebServiceException: 未定義的連接埠類型: {http://test.hello/}IHelloWS」或者「javax.xml.ws.WebServiceException: {http://test.hello/}HelloWSService 不是有效的連接埠. 有效的連接埠為: {http://test.hello/}HelloWSPort」,應該是一定要有interface了

※Eclipse會多一個Console,一個是伺服器端的;一個是用戶端的
但hello()是伺服器端的程式碼,所以「Hello WebService!」會印在伺服器端


※wsimport

在安裝java的路徑裡,例如C:\Program Files\Java\jdkXXX\bin,有一個wsimport工具(jre沒有)
它可以將我們剛剛寫的程式顯示出完整的程式碼,因為有很多預設值,所以上面的程式碼才會這麼少

此時在命令提示字元下使用這個命令:
wsimport -d d:\ws_practice -keep -verbose wsdl網址
-d後面接路徑,表示會將生成出來的.class檔放在這個路徑裡,不指定會在目前的目錄生成
-keep表示還會保留.java檔
-verbose表示生成的過程有畫面可以看



此時切換好路徑後,輸入 wsimport -keep http://localhost:8888/aaa/bbb/ccc?wsdl
新增一個project,package和之前一樣,然後將生成的程式碼複製進去,.java部分即可
後面的網址也可以是wsdl格式的檔案,例如將這段網址的結果複製到一個文字文件,然後下
wsimport -keep -d d:\xxx d:\wsdl_file.wsdl,也是可以,副檔不一定要wsdl,只要是純文字即可

※test.java

HelloWSService hws = new HelloWSService();
IHelloWS hw = hws.getHelloWSPort();
System.out.println(hw.getClass());
hw.hello();

※這支是使用wsimport幫我們生成的程式碼,測試用戶端的程式

※首先先用wsdl最下面的service的name屬性名稱,然後一定有個方法可以返回介面
service下有個port標籤,他的name屬性就是getXXX
所以以這個例子就是getHelloWSPort

※可以發現 hw.getClass()是用代理的



※TCP/IP Monitor

Eclipse有內鍵一個TCP/IP監控工具
首先將網址的wsdl存成一個檔案,然後將最下面的代碼改成自己監聽的port
<service name="HelloWSService">
    <port name="HelloWSPort" binding="tns:HelloWSPortBinding">
        <soap:address location="http://localhost:9999/aaa/bbb/ccc"/>
    </port>
</service>

※我是將8888改成9999

最後在Window-->Preferences,然後如下畫面設定:

※綠框是新改的port,紅框的部分只要打IP和port即可



設定後還要啟動才可以
如果要修改,必需先按左邊的設定,然後右邊的Stop,再按Edit

※程式執行和wsimport的test.java一樣,執行後會出現一個叫TCP/IP Monitor的View
如果沒出現,就要選Window-->Show View-->Other...後,會出現如下的畫面:


TCP/IP Monitor畫面如下:

左上的紅框是只要有一次請求就會出現一次,不管錯誤還是一樣的請求都會出現
左邊為Request;右邊為Response
選了XML就會出現最下面的SOAP格式的標籤,這個標籤在上面介紹的Web Services Exporer也有,如果選Web Browser會比較好看



※SOAP UI 工具

此工具的功能和Eclipse 內鍵的 Web Services Explorer一樣,可以分析wsdl的結構,但工作中還蠻流行的,在此介紹一下,這裡是官網,可到此下載

P.S 上面介紹的wsimport將wsdl轉成java代碼,可以在網路上搜尋「免費 webservice」,可以找到xxx.wsdl的網址,但不一定能成功轉成java代碼,因為有可能是其他語言產生出來的
但用Eclipse 內鍵的 Web Services Explorer 和 SOAP UI 都可以執行

※為了要測試有參數的情形,所以介面增加一個方法並實作
public String add(Integer a, int b);
--------------------
@Override
public String add(Integer a, int b) {
    System.out.println("call add method!");
    return "Answer is " + (a + b);
}

※安裝好後如下設定:

有以下四種方法可以開啟打xxx.wsdl的地方
1.按紅框的SOAP,
2.Project按右鍵--> New SOAP Project
3.File--> New SOAP Project
4.Ctrl+N




在紅框打上xxx.wsdl後,Project Name會自動代出值來,預設是帶最後的「/」之後的字串,可以改




最左邊可看到兩個方法,add是我剛才加的,按下去會出現右邊的圖
argX是打參數的地方,打完後按下綠色的三角形就會執行
最右邊就會出現結果,如果有在控制台打印字串,也會出現在 server 端的 console