使用Sinatra創建Soap Web Service

簡單對象訪問協議(SOAP)是一種輕量的、簡單的、基於 XML 的協議,它被設計成在 WEB 上交換結構化的和固化的信息。 SOAP 可以和現存的許多因特網協議和格式結合使用,包括超文本傳輸協議( HTTP),簡單郵件傳輸協議(SMTP),多用途網際郵件擴充協議(MIME)。它還支持從消息系統到遠程過程調用(RPC)等大量的應用程序。

本文中得示例是Soap與Http結合的Web service。其實現了一個簡單的Echo和Reverse Echo的服務,既請求原文返回和請求倒序返回。就純技術層面來說,本質上就是post 一個製造xml格式的請求,獲得一個xml格式的response的Http Request。

首先,使用xsd明確request和response的xml結構

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema targetNamespace="http://www.xianlinbox.net/echo" xmlns="http://www.xianlinbox.com/echo" xmlns:xs="http://www.w3.org/2001/XMLSchema" attributeFormDefault="qualified" elementFormDefault="qualified">
<xs:complexType name="EchoMessageType">
<xs:sequence>
<xs:element name="Message" type="xs:string" minOccurs="1" />
</xs:sequence>
</xs:complexType>
<xs:element name="EchoRequest" type="EchoMessageType"/>
<xs:element name="EchoResponse" type="EchoMessageType"/>
<xs:element name="ReverseEchoRequest" type="EchoMessageType"/>
<xs:element name="ReverseEchoResponse" type="EchoMessageType"/>
</xs:schema>

然後,創建http服務

require 'bundler'
Bundler.setup
require 'sinatra/base'
# XML 解析需用到nokogiri
require 'nokogiri'
require 'builder'

class EchoService < Sinatra::Base

set :root, File.dirname(__FILE__)


# 添加http basic authentication
use Rack::Auth::Basic, "Restricted Area" do |username, password|
username == 'admin' and password == 'admin'
end

# 創建Soap消息異常對象
module SoapFault
class MustUnderstandError < StandardError
def fault_code
"MustUnderstand"
end
end

class ClientError < StandardError
def fault_code
"Client"
end
end
end
# SOAP要求SOAP messages使用Content-Type:text/xml,Sinatra默認的是application/xml,因此需配置。
configure do
mime_type :xml, "text/xml"
end

#xsd讀取request和response的schema, xslt用於從soap message中獲取消息體,
def initialize(*args)
@xsd = Nokogiri::XML::Schema(File.read("#{File.dirname(__FILE__)}/public/echo_service.xsd"))
@xslt = Nokogiri::XSLT(File.read("#{File.dirname(__FILE__)}/lib/soap_body.xslt"))
super
end

# SOAP endpoint
post '/echo_service' do
begin
soap_message = Nokogiri::XML(request.body.read)
# 通過消息頭來判斷是否是當前服務可處理的消息。
raise(SoapFault::MustUnderstandError, "SOAP Must Understand Error", "MustUnderstand") if soap_message.root.at_xpath('//soap:Header/*[@soap:mustUnderstand="1" and not(@soap:actor)]', 'soap' => 'http://schemas.xmlsoap.org/soap/envelope/')
# 獲取消息體
soap_body = @xslt.transform(soap_message)
# 通過schema驗證request的結構是否合法。
errors = @xsd.validate(soap_body).map { |e| e.message }.join(", ")
# 如果request格式不正確,拋錯。
raise(SoapFault::ClientError, errors) unless errors == ""
# 從request中獲取該request請求的操作,通過動態分發的方式發送到對應的處理方法。
self.send(soap_operation_to_method(soap_body), soap_body)
rescue StandardError => e
fault_code = e.respond_to?(:fault_code) ? e.fault_code : "Server"
halt(500, builder(:fault, :locals => {:fault_string => e.message, :fault_code => fault_code}))
end
end

private

# 解析消息體獲取request請求的操作。
def soap_operation_to_method(soap_body)
method = soap_body.root.name.sub(/Request$/, '').gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase.to_sym
end

# Echo服務的具體處理函數
def echo(soap_body)
message = soap_body.root.at_xpath('//echo:Message/text()', 'echo' => 'http://www.without-brains.net/echo').to_s
builder(:echo_response, :locals => {:message => message})
end

# ReverseEcho服務的具體處理函數
def reverse_echo(soap_body)
message = soap_body.root.at_xpath('//echo:Message/text()', 'echo' => 'http://www.without-brains.net/echo').to_s.reverse!
builder(:reverse_echo_response, :locals => {:message => message})
end
end


通過使用builder可以更方便的構建response的內容,
Fault Builder
xml.SOAP(:Envelope, "xmlns:SOAP" => "http://schemas.xmlsoap.org/soap/envelope/") do
xml.SOAP :Body do
xml.SOAP :Fault do
xml.faultcode "SOAP:#{fault_code}"
xml.faultstring fault_string
end
end
end


EchoResponse Builder
xml.SOAP(:Envelope, "xmlns:SOAP" => "http://schemas.xmlsoap.org/soap/envelope/", "xmlns:echo" => "http://www.without-brains.net/echo") do
xml.SOAP :Body do
xml.echo :EchoResponse do
xml.echo(:Message, message)
end
end
end


ReverseEchoRespose Builder
xml.SOAP(:Envelope, "xmlns:SOAP" => "http://schemas.xmlsoap.org/soap/envelope/", "xmlns:echo" => "http://www.without-brains.net/echo") do
xml.SOAP :Body do
xml.echo :ReverseEchoResponse do
xml.echo(:Message, message)
end
end
end


That's all!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章