Web Service技术内幕

系统 1676 0

Web Service 技术内幕

--Web Service 的详细教程和协议分析

2010/2/25 蒋彪 于南京

1. Web Service 的介

1.1 Web Service 到底是什

研究一下当前的 用程序 开发 ,你会 发现 一个 绝对 向:人 们开 始偏 基于 浏览 器的瘦客 户应 用程序。 当然不是因 瘦客 提供更好的用 界面,而是因 它能 避免花在桌面 用程序 布上的高成本。 布桌面 用程序成本很高,一半是因 为应 用程序安装和配置的 问题 ,另一半是因 和服 器之 通信的 问题

传统 Windows 富客 户应 用程序使用 DCOM 来与服 行通信和 象。配置好 DCOM 使其在一个大型的网 中正常工作将是一个极富挑 性的工作,同 也是 IT 工程 的噩梦。事 上, IT 工程 宁愿忍受 浏览 器所 来的功能限制,也不愿在局域网上去运行一个 DCOM 。在我看来, 果就是一个 布容易,但 开发难 度大而且用 界面极其受限的 用程序。极端的 ,就是你花了更多的 金和 时间 ,却 开发 出从用 看来功能更弱的 用程序。不信 问问 你的会 计师对 新的基于 浏览 器的会 计软 件有什 想法: 大多数商用程序用 希望使用更加友好的 Windows 界面。

于客 端与服 器的通信 问题 ,一个完美的解决方法是使用 HTTP 协议 来通信。 是因 任何运行 Web 浏览 的机器都在使用 HTTP 协议 。同 ,当前 多防火 也配置 只允 HTTP 接。

多商用程序 另一个 问题 ,那就是与其他程序的互操作性。如果所有的 用程序都是使用 COM .NET 言写的,并且都运行在 Windows 平台上,那就天下太平了。然而,事 上大多数商 数据仍然在大型主机上以非 系文件 (VSAM) 的形式存放,并由 COBOL 写的大型机程序 访问 。而且,目前 有很多商用程序 继续 在使用 C++ Java Visual Basic 和其他各 写。 在,除了最 简单 的程序之外,所有的 用程序都需要与运行在其他异构平台上的 用程序集成并 行数据交 这样 的任 通常都是由特殊的方法,如文件 传输 和分析,消息 列, 适用于某些情况的的 API ,如 IBM " 程序到程序交流 (APPC)" 等来完成的。在以前,没有一个 用程序通信 准,是独立于平台、 建模型和 言的。只有通 Web Service ,客 端和服 器才能 自由的用 HTTP 行通信,不 两个程序的平台和 言是什

Web Service

对这 问题 ,我 至少有两 答案。从表面上看, Web Service 就是一个 用程序,它向外界暴露出一个能 Web 用的 API 就是 ,你能 程的方法通 Web 用程序。我 Web Service 用程序叫做客 。例如,你想 建一个 Web Service ,它的作用是返回当前的天气情况。那 你可已建立一个 ASP 面,它接受 编码 为查询 字符串,然后返回一个由逗号隔 的字符串,包含了当前的气温和天气。要 ASP 面,客 端需要 送下面的 HTTP GET 求:

http://host.company.com/weather.asp?zipcode=20171

  返回的数据就 应该 这样

ASP 面就 应该 可以算作是 Web Service 了。因 它基于 HTTP GET 求,暴露出了一个可以通 Web 用的 API 。当然, Web Service 有更多的 西。

  下面是 Web Service 更精确的解 Web Service s 是建立可互操作的分布式 用程序的新平台。作 一个 Windows 程序 ,你可能已 COM DCOM 建立 基于 件的分布式 用程序。 COM 是一个非常好的 件技 ,但是我 也很容易 COM 并不能 足要求的情况。

Web Service 平台是一套 准,它定 用程序如何在 Web 实现 互操作性。你可以用任何你喜 言,在任何你喜 的平台上写 Web Service ,只要我 可以通 Web Service 对这 些服 务进 查询 访问

新平台

Web Service 平台需要一套 协议 实现 分布式 用程序的 建。任何平台都有它的数据表示方法和 型系 。要 实现 互操作性, Web Service 平台必 提供一套 准的 型系 ,用于沟通不同平台、 言和 件模型中的不同 型系 。在 传统 的分布式系 中,基于界面 (interface) 的平台提供了一些方法来描述界面、方法和参数 ( 注:如 COM COBAR 中的 IDL ) 。同 的, Web Service 平台也必 提供一 种标 准来描述 Web Service 可以得到足 的信息来 Web Service 。最后,我 们还 有一 方法来 对这 Web Service 用。 这种 方法 实际 是一 种远 协议 (RPC) 了达到互操作性, 这种 RPC 协议还 与平台和 言无 下面几个小 要介 Web Service 平台的 三个技

XML XSD

  可 展的 标记语 (XML) Web Service 平台中表示数据的基本格式。除了易于建立和易于分析外, XML 主要的 点在于它既是平台无 的,又是厂商无 的。无 性是比技 术优 越性更重要的: 件厂商是不会 选择 一个由 手所 明的技 的。

XML 解决了数据表示的 问题 ,但它没有定 一套 准的数据 型,更没有 套数据 型。例如,整形数到底代表什 ?16 位, 32 位, 64 ? 细节对实现 互操作性都是很重要的。 W3C 制定的 XML Schema(XSD) 就是 专门 解决 问题 的一套 准。它定 了一套 准的数据 型,并 出了一 种语 言来 套数据 型。 Web Service 平台就是用 XSD 来作 其数据 型系 的。当你用某 种语 ( VB.NET C#) 来构造一个 Web Service 了符合 Web Service 准,所有你使用的数据 型都必 转换为 XSD 型。 你用的工具可能已 帮你完成了 转换 ,但你很可能会根据你的需要修改一下 转换过 程。在第二章中,我 将深入 XSD ,学 样转换 自定 的数据 ( 例如 ) XSD 型。

SOAP

Web Service 建好以后,你或者其他人就会去 用它。 简单对 访问协议 (SOAP) 提供了 准的 RPC 方法来 Web Service 实际 上, SOAP 里有点用 不当:它意味着下面的 Web Service 是以 象的方式表示的,但事 并不一定如此:你完全可以把你的 Web Service 写成一系列的 C 函数,并仍然使用 SOAP 用。 SOAP 范定 SOAP 消息的格式,以及怎 HTTP 协议 来使用 SOAP SOAP 也是基于 XML XSD 的, XML SOAP 的数据 编码 方式。第三章我 讨论 SOAP ,并 结识 SOAP 消息的各 元素。

WSDL

  你会怎 人介 你的 Web Service 有什 功能,以及 个函数 的参数呢 ? 你可能会自己写一套文档,你甚至可能会口 上告 需要使用你的 Web Service 的人。 些非正式的方法至少都有一个 重的 问题 :当程序 坐到 电脑 前,想要使用你的 Web Service 候,他 的工具 ( Visual Studio) 无法 提供任 何帮助,因 为这 些工具根本就不了解你的 Web

service 。解决方法是:用机器能 阅读 的方式提供一个正式的描述文档。 Web Service 描述 (WSDL) 就是 这样 一个基于 XML 言,用于描述 Web Service 及其函数、参数和返回 。因 是基于 XML 的,所以 WSDL 既是机器可 阅读 的,又是人可 阅读 的, 将是一个很大的好 。一些最新的 开发 工具既能根据你的 Web Service 生成 WSDL 文档,又能 WSDL 文档,生成 用相 Web Service 的代

一句话总结: Web Service 就是高级版的 DCOM CORBA

1.2 Web Service 的体系

通过上图,我们能看出来, WebService 的运行机制有点类似于 CORBA

1. 通过 WSDL 描述 WebService

2. 通过 SOAP 在互联网环境内传递数据

3. 通过 JAXB 实现 Java XML 之间的互相转换。

具体的 Web Service 的体系结构可以参照以下文件:

======================================================

深入浅出 JAX-WS 2.0

http://blog.csdn.net/nanjingjiangbiao/archive/2010/02/11/5306034.aspx

SOA 研究之 JAX-WS

http://blog.csdn.net/nanjingjiangbiao/archive/2010/02/10/5305049.aspx

手把手教你在 Interstage 上部署 WebService

http://blog.csdn.net/nanjingjiangbiao/archive/2010/02/22/5317356.aspx

======================================================

.

1.3 一个 简单 WebService 运行

WebService 代码:

package stock.server ;

@ javax.jws. WebService

public class StockQuoteProvider {

public StockQuoteProvider () {

}

public float getLastTradePrice (String tickerSymbol) {

return "abc" .equals(tickerSymbol)? 1234.0f : 0.0f;

}

}

客户端代码:

import java.lang.annotation.Annotation ;

import stock.server.*;

import javax.xml.ws.Service ;

public class StockQuoteClient {

public static void main(String[] args) throws Exception {

StockQuoteProviderService service = new StockQuoteProviderService();

StockQuoteProvider port = service.getStockQuoteProviderPort();

System. out .println(port.getLastTradePrice(args[0]));

}

}

2. Web Service 研究的 境准

2.1 安装运行

推荐安装 GlassFish 服务器,具体安装方法可以参见以下文章:

手把手教你 安装 GlassFish

http://blog.csdn.net/nanjingjiangbiao/archive/2010/01/28/5264913.aspx

2. 2 下载 WebService API(JAX-WS) 的源代码

具体的下载 URL 参见以下地址:

https://jax-ws-sources.dev.java.net/source/browse/jax-ws-sources/

2. 3 部署服务器端

1. 1.3 中的服务器端部署到 GlassFish

2. wsimport 命令生成 stub 中间程序

3. 1.3 中的客户端代码和 stub 代码,以及 2.2 下载的 WebService 源代码部署到 Eclipse 工程中,就可以 DEBUG 了。

具体的可以参照以下文章:

SOA 研究之 JAX-WS

http://blog.csdn.net/nanjingjiangbiao/archive/2010/02/10/5305049.aspx

3. Web Service 的技 内幕

3.1 端是如何 WSDL 建立 系的?

1. 首先,我们看到客户端程序中有以下这样一行

StockQuoteProviderService service = new

StockQuoteProviderService();

2. 这行代码,会调用到

public StockQuoteProviderService() {

super ( STOCKQUOTEPROVIDERSERVICE_WSDL_LOCATION , new QName( "http://server.stock/" , "StockQuoteProviderService" ));

}

3. 这行代码,会调用到 c om.sun.xml.ws.spi.ProviderImpl

@Override

public ServiceDelegate createServiceDelegate( URL wsdlDocumentLocation, QName serviceName, Class serviceClass) {

return new WSServiceDelegate(wsdlDocumentLocation, serviceName, serviceClass );

}

4. 这行代码,会调用到 com.sun.xml.ws.clientWSServiceDelegate

/**

* @param serviceClass

* Either {@link Service} .class or other generated service - derived classes.

*/

public WSServiceDelegate( @Nullable Source wsdl, @NotNull QName serviceName, @NotNull final Class<? extends Service> serviceClass) {

~省略~

}

5 . 而其中最关键的就是以下这段,解析阅读 WSDL 的代码,这段代码主要是用 XMLStream 来构建 WSDL 代码,不足为奇。

WSDLServiceImpl service= null ;

if (wsdl != null ) {

try {

URL url = wsdl.getSystemId()== null ? null : new URL(wsdl.getSystemId());

WSDLModelImpl model = parseWSDL(url, wsdl);

service = model.getService( this . serviceName );

if (service == null )

throw new WebServiceException(

ClientMessages. INVALID_SERVICE_NAME ( this . serviceName ,

buildNameList(model.getServices().keySet())));

// fill in statically known ports

for (WSDLPortImpl port : service.getPorts())

ports .put(port.getName(), new PortInfo( this , port));

} catch (MalformedURLException e) {

throw new WebServiceException(ClientMessages. INVALID_WSDL_URL (wsdl.getSystemId()), e);

}

}

this . wsdlService = service;

3. 2 端是如何 SEI 建立 系的?

1. 首先,我们看到客户端程序中有以下这样一行

StockQuoteProvider port = service.getStockQuoteProviderPort();

2. 这行代码,会调用到 com.sun.xml.ws.clientWSServiceDelegate

private <T> T getPort (WSEndpointReference wsepr, QName portName, Class<T> portInterface,

WebServiceFeature... features) {

SEIPortInfo spi = addSEI(portName, portInterface, features);

return createEndpointIFBaseProxy(wsepr,portName,portInterface,features, spi);

}

3. 这行代码,会调用到 com.sun.xml.ws.clientWSServiceDelegate

/**

* Creates a new pipeline for the given port name.

*/

private Tube createPipeline (PortInfo portInfo, WSBinding binding) {

//Check all required WSDL extensions are understood

checkAllWSDLExtensionsUnderstood(portInfo,binding);

SEIModel seiModel = null ;

if (portInfo instanceof SEIPortInfo) {

seiModel = ((SEIPortInfo)portInfo). model ;

}

BindingID bindingId = portInfo. bindingId ;

TubelineAssembler assembler = TubelineAssemblerFactory.create(

Thread. currentThread ().getContextClassLoader(), bindingId) ;

if (assembler == null )

throw new WebServiceException( "Unable to process bindingID=" + bindingId); // TODO : i18n

return assembler.createClient(

new ClientTubeAssemblerContext(

portInfo. targetEndpoint ,

portInfo. portModel ,

this , binding, container ,((BindingImpl)binding).createCodec(),seiModel));

}

在这段代码中,使用了 TUBE 技术,把客户端和服务器之间建立了关系。

4. 这行代码,会调用到 com.sun.xml.ws.util.pipe .StandaloneTubeAssembler

@NotNull

public Tube createClient(ClientTubeAssemblerContext context) {

Tube head = context.createTransportTube();

head = context.createSecurityTube(head);

if ( dump ) {

// for debugging inject a dump pipe. this is left in the production code,

// as it would be very handy for a trouble-shooting at the production site.

head = context.createDumpTube( "client" , System. out , head);

}

head = context.createWsaTube(head);

head = context.createClientMUTube(head);

head = context.createValidationTube(head);

return context. createHandlerTube (head);

}

在以上代码中,开始和 SOAP 绑定,准备发送请求和调用函数等等。

3. 3 端是如何 发送请求的

1. 首先,我们看到客户端程序中有以下这样一行

port.getLastTradePrice(args[0])

2. 这行代码,会调用到 com.sun.xml.ws.client.sei.SEIStub

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

MethodHandler handler = methodHandlers .get(method);

if (handler != null ) {

return handler.invoke(proxy, args);

} else {

// we handle the other method invocations by ourselves

try {

return method.invoke( this , args);

} catch (IllegalAccessException e) {

// impossible

throw new AssertionError(e);

} catch (IllegalArgumentException e) {

throw new AssertionError(e);

} catch (InvocationTargetException e) {

throw e.getCause();

}

}

}

3. 这行代码,会调用到 com.sun.xml.ws.client. s tub ( 中间省去了 2 层不重要的代码 )

/**

* Passes a message to a pipe for processing.

* <p>

* Unlike {@link Tube} instances,

* this method is thread - safe and can be invoked from

* multiple threads concurrently.

*

* @param packet The message to be sent to the server

* @param requestContext The {@link RequestContext} when this invocation is originally scheduled.

* This must be the same object as {@link #requestContext} for synchronous

* invocations, but for asynchronous invocations, it needs to be a snapshot

* captured at the point of invocation, to correctly satisfy the spec requirement.

* @param receiver Receives the {@link ResponseContext} . Since the spec requires

* that the asynchronous invocations must not update response context,

* depending on the mode of invocation they have to go to different places.

* So we take a setter that abstracts that away.

*/

protected final Packet process(Packet packet, RequestContext requestContext, ResponseContextReceiver receiver) {

configureRequestPacket(packet, requestContext);

Pool<Tube> pool = tubes ;

if (pool == null )

throw new WebServiceException( "close method has already been invoked" ); // TODO : i18n

Fiber fiber = engine . createFiber ();

// then send it away!

Tube tube = pool.take();

try {

return fiber.runSync(tube, packet);

} finally {

// this allows us to capture the packet even when the call failed with an exception.

// when the call fails with an exception it's no longer a 'reply' but it may provide some information

// about what went wrong.

// note that Packet can still be updated after

// ResponseContext is created.

Packet reply = (fiber.getPacket() == null ) ? packet : fiber.getPacket();

receiver.setResponseContext( new ResponseContext(reply));

pool.recycle(tube);

}

}

4. 这行代码,会调用到 com.sun.xml.ws.api.pipe . __doRun ( 中间省去了 3 层不重要的代码 )

这段代码开始发送打成数据包的 http 请求

/**

* To be invoked from {@link #doRun(Tube)} .

*

* @see #doRun(Tube)

*/

private Tube __doRun(Tube next) {

final Fiber old = CURRENT_FIBER .get();

CURRENT_FIBER .set( this );

// if true, lots of debug messages to show what's being executed

final boolean traceEnabled = LOGGER .isLoggable(Level. FINER );

try {

while (!isBlocking() && ! needsToReenter ) {

try {

NextAction na;

Tube last;

if ( throwable != null ) {

if ( contsSize ==0) {

// nothing else to execute. we are done.

return null ;

}

last = popCont();

if (traceEnabled)

LOGGER .finer(getName()+ ' ' +last+ ".processException(" + throwable + ')' );

na = last.processException( throwable );

} else {

if (next!= null ) {

if (traceEnabled)

LOGGER .finer(getName()+ ' ' +next+ ".processRequest(" + packet + ')' );

na = next.processRequest( packet );

last = next;

} else {

if ( contsSize ==0) {

// nothing else to execute. we are done.

return null ;

}

last = popCont();

if (traceEnabled)

LOGGER .finer(getName()+ ' ' +last+ ".processResponse(" + packet + ')' );

na = last.processResponse( packet );

}

}

~省略~

5. 这行代码,会调用到 com.sun.xml.ws. encoding . StreamSOAPCodec ( 中间省去了无数层不重要的代码 )

public ContentType encode(Packet packet, OutputStream out) {

if (packet.getMessage() != null ) {

XMLStreamWriter writer = XMLStreamWriterFactory. create (out);

try {

packet.getMessage().writeTo(writer);

writer.flush();

} catch (XMLStreamException e) {

throw new WebServiceException(e);

}

XMLStreamWriterFactory. recycle (writer);

}

return getContentType(packet. soapAction );

}

大家可以看到,他发出请求了。

3. 4 服务器端 是如何 接受请求的

1. 首先,服务器端有一个名叫 JAXWSServlet Servlet 常驻服务器,监听请求。所以,请求会首先被转发给 com.sun.enterprise.webservice.JAXWSServlet

protected void doPost (HttpServletRequest request,

HttpServletResponse response)

throws ServletException ,IOException{

/**

* This requirement came from the jbi team. If the WebServiceEndpoint

* is a jbi endpoint which is private throw an error whenever a get

* or a post request is made

*/

Endpoint endpt =

wsEngine_ .getEndpoint(request.getServletPath());

~省略~

2. 最后,代码会调转到以下 com.sun.xml.ws.transport.http.HttpAdapter

final class HttpToolkit extends Adapter.Toolkit {

public void handle (WSHTTPConnection con) throws IOException {

boolean invoke = false ;

try {

Packet packet = new Packet();

try {

packet = decodePacket(con, codec );

invoke = true ;

} catch (ExceptionHasMessage e) {

LOGGER .log(Level. SEVERE , "JAXWS2015: An ExceptionHasMessage occurred. " + e.getMessage(), e);

packet.setMessage(e.getFaultMessage());

} catch (UnsupportedMediaException e) {

LOGGER .log(Level. SEVERE , "JAXWS2016: An UnsupportedMediaException occurred. " + e.getMessage(), e);

con.setStatus(WSHTTPConnection. UNSUPPORTED_MEDIA );

} catch (Exception e) {

LOGGER .log(Level. SEVERE , "JAXWS2017: A ServerRtException occurred. " + e.getMessage(), e);

con.setStatus(HttpURLConnection. HTTP_INTERNAL_ERROR );

}

if (invoke) {

try {

packet = head .process(packet, con.getWebServiceContextDelegate(),

packet. transportBackChannel );

} catch (Exception e) {

LOGGER .log(Level. SEVERE , "JAXWS2018: An Exception occurred. " + e.getMessage(), e);

if (!con.isClosed()) {

writeInternalServerError(con);

}

return ;

}

}

encodePacket(packet, con, codec );

} finally {

if (!con.isClosed()) {

con.close();

}

}

}

}

以上代码分为三块大的处理,分别是

packet = decodePacket(con, codec );

packet = head .process(packet, con.getWebServiceContextDelegate(),

packet. transportBackChannel );

encodePacket(packet, con, codec );

用来解析数据包,调用服务,发送回复数据包。这里就不详细介绍了。

4. 总结

希望能够通过这样的文章,更清晰的,直白的把WebService的详细技术内幕公布给大家,为开源软件运动作一份自己的贡献。

## 以上 ##

Web Service技术内幕


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论