Web Service技术内幕

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++JavaVisual 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 Services是建立可互操作的分布式用程序的新平台。作一个Windows程序,你可能已COMDCOM建立基于件的分布式用程序。COM是一个非常好的件技,但是我也很容易COM并不能足要求的情况。

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

  新平台

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

  XMLXSD

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

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

  SOAP

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

1.2 Web Service的体系

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

1.通过WSDL描述WebService

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

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

具体的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. 这行代码,会调用到com.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.stub(中间省去了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. 首先,服务器端有一个名叫JAXWSServletServlet常驻服务器,监听请求。所以,请求会首先被转发给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的详细技术内幕公布给大家,为开源软件运动作一份自己的贡献。

##以上##

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值