http使用restful代理soap

简介: 作为建立在http协议之上的数据协议,soap定义了数据报文的格式与规范与应用程序的调用方式(RPC模式),隐藏了网络传输的细节,这里采用restful的风格来请求webservice服务

标题:http使用restful代理soap协议

引言:作为建立在http协议之上的数据协议,soap定义了数据报文的格式与规范与应用程序的调用方式(RPC模式),即隐藏了网络传输的细节。

1、soap

simple object accessProtocol:简单的对象访问协议,采用XML格式来作为数据报文,拥有着RPC的特性,所以注定适用于早期的分布式计算环境中。

1.1、对比http

两个概念,http是应用协议,soap是基于http之上的数据协议。http只负责数据的传递,无论数据是图片,网页,文件……,在流转链路中,统一以文本方式处理。soap定义了一种规约:如何把请求&响应传递的XML文本数据,解析成对象。

总结下来就是:soap = rpc模式 + http传输 + xml报文,硬说区别可以列举如下三点:

  • http是一种超文本传输协议,规定的传输内容皆为文本;
  • soap是http的高级应用,以结构化的xml数据来作为报文;
  • server 与 client存在格式化解析报文,故完整的请求流程会牺牲一点性能。

1.2、webservice

soap基础上的实际应用,早期的分布式应用程序。下面以java为例,分别搭建server 和client。

1.2.1、server

采用JAX-WS框架快速构建的方式搭建一个webservice server。

对外提供一个API:sayHelloWorldFrom

package example;

import javax.jws.WebMethod;
import javax.jws.WebService;
import javax.xml.ws.Endpoint;

/**
 * @ClassName ${NAME}
 * @Description TODO
 * @Date 2022/8/15 2:15 H
 * @Author ikejcwang
 * @Version 1.0.0
 **/
@WebService()
public class HelloWorld {

    @WebMethod
    public String sayHelloWorldFrom(String from) {
        String result = "Hello, world, from " + from;
        System.out.println(result);
        return result;
    }

    public static void main(String[] argv) {
        Object implementor = new HelloWorld();
        String address = "http://localhost:9000/HelloWorld";
        Endpoint.publish(address, implementor);
    }
}

启动main函数后,浏览器打开链接:http://localhost:9000/HelloWorld?wsdl,可以查阅server对外提供的wsdl文档

1.2.2、wsdl

框架内置了wsdl生成,作为介绍该webservice server的功能特点,对外提供了哪些函数API,这些函数的参数结构,约束条件,参数如何组装成soap报文。方便客户端无障碍调用;

1.2.3、client

采用JAX-WS框架针对特定的wsdlURL,快速自动生成客户端的class。如下所示:

└── src
    ├── example
    │   └── HelloWorldClient.java
    └── vip
        └── wangjc
            ├── HelloWorld.class
            ├── HelloWorld.java
            ├── HelloWorld.wsdl
            ├── HelloWorldService.class
            ├── HelloWorldService.java
            ├── ObjectFactory.class
            ├── ObjectFactory.java
            ├── SayHelloWorldFrom.class
            ├── SayHelloWorldFrom.java
            ├── SayHelloWorldFromResponse.class
            ├── SayHelloWorldFromResponse.java
            ├── package-info.class
            └── package-info.java

main函数如下:

package example;

import vip.wangjc.HelloWorld;
import vip.wangjc.HelloWorldService;

/**
 * @ClassName ${NAME}
 * @Description TODO
 * @Date 2022/8/15 3:07 H
 * @Author ikejcwang
 * @Version 1.0.0
 **/
public class HelloWorldClient {
    public static void main(String[] argv) {
        HelloWorld service = new HelloWorldService().getHelloWorldPort();
        //invoke business method
        String result = service.sayHelloWorldFrom("haha ikejcwang");    // 远程调用的逻辑实现
        System.out.println(result);

        try {
            Thread.sleep(20000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

1.2.4、抓包

启动server,然后继续启动client,通过抓包采集下client发送的报文,如图所示

soap_抓包.png

根据抓包信息来看,可以推测发现,client是先获取wsdl文档,将wsdl文档缓存在当前内存中,然后根据wsdl文档提供的规约来将需要调用的对象组装成XML文本,然后发起http请求。

为什么会说它会将wsdl文档缓存起来?可以修改client->main函数,调用两次sayHelloWorldFrom,会发现只有在最开始时,或是程序启动时访问了/url?wsdl链接,后续都是直接发起http业务请求的;

数据报文如下:

<?xml version='1.0' encoding='UTF-8'?>
<S:Envelope
    xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
    <S:Body>
        <ns2:sayHelloWorldFrom
            xmlns:ns2="http://example/">
            <arg0>haha ikejcwang</arg0>
        </ns2:sayHelloWorldFrom>
    </S:Body>
</S:Envelope>

1.2.5、post调用?

既然知道,它是基于http协议发起调用的,也通过抓包拿到了它的请求报文,按理,postman或者任意的http client都可以发起调用;

soap_postman.png

如上所示,调用正常,拿到的原始响应报文(XML结构)。webservice client自行会根据wsdl文档对原始响应报文做XML->对象的处理。

2、http Rest Proxy

既然上述知道,是先通过获取wsdl文档完成后续的传输文本编解码,那么应该也可以搭一个http代理,在代理内部完成编解码的任务,支持外部json数据,或者xml原始数据,通过这个http代理server都能成功请求soap server。

2.1、实现逻辑

我们选择在http server内部构建soap client对象,对外选择屏蔽wsdl,需要一个配置文件,来管理多个soap server,配置文件的结构如下:

{
  "userManager": {
    "server": "http://localhost:9000/HelloWorld"
  }
}

http client发起请求时,URL结构上一级path代表userManager,锁定需要访问的哪个soap server,二级path代表method`,调用指定soap server的哪个函数名。

2.2、http server

如下代码,思路应该比较简洁,

  1. 启动入口为start,先初始化soap配置文件,再开启http服务,监听请求;
  2. 初始化soapClient,遍历config配置,依次初始化后存入到soapCache对象中;
  3. 开启http服务,并监听请求事件,请求触发时,如果soapConfig为空,就返回500;
  4. 获取请求的pathname,按/分割,要求必须/${userManager}/${methodName},结尾不能带/,否则依次返回500&错误信息;
  5. 从请求中读取请求报文;
  6. 拿到报文body 和 二级path的methodName,根据一级path筛选的soap对象,去调用soap服务;
  7. 做出http响应,200 & 500,正常报文 & error info
/**
 * create by ikejcwang on 2022.03.07.
 * 注:这只是一个demo,没有适配高并发,如果想要引用的话,可以根据nodejs多进程实现方案来具体改造。
 */
'use strict';
const http = require('http');
const nodeUtil = require('util');
const URL = require('url');
const SoapClient = require('./SoapClient');
const settings = require('./settings').settings;
const configs = require('./settings').configs;
let soapCache = {}

start();

/**
 * 启动入口
 */
function start() {
    initSoapConfig();
    startHttpServer();
}

/**
 * 初始化Soap配置
 */
function initSoapConfig() {
    if (configs && Object.keys(configs).length > 0) {
        for (let key in configs) {
            soapCache[key] = new SoapClient(configs[key]);
        }
    }
}

/**
 * 启动http服务
 */
function startHttpServer() {
    let server = http.createServer();
    server.on('request', listenRequestEvent);
    server.on('close', () => {
        console.log('http Server has Stopped At:' + settings['bindPort'])
    });
    server.on('error', err => {
        console.log('http Server error:' + err.toString());
        setTimeout(() => {
            process.exit(1);
        }, 3000);
    });
    server.listen(settings['bindPort'], settings['bindIP'], settings['backlog'] || 8191, () => {
        console.log('Started Http Server At: ' + settings['bindIP'] + ':' + settings['bindPort'])
    })
}

/**
 * 监听request事件
 * @param request
 * @param response
 */
async function listenRequestEvent(request, response) {
    request.on('aborted', () => {
        console.error('aborted: client request aborted')
    });
    request.on('finish', () => {
        console.log('request has finished');
    })
    request.on('error', (err) => {
        console.log(`error event: ${nodeUtil.inspect(err)}`)
    })
    try {
        if (!configs || Object.keys(configs).length < 1) {
            response.statusCode = 500;
            response.setHeader('content-type', 'text/plain; charset=utf-8');
            response.end('No Soap Config');
            return;
        }
        let sourceUrl = URL.parse(request.url, true);
        let pathArr = sourceUrl.pathname.split('/').splice(1);
        if (pathArr.length < 2 || !pathArr[pathArr.length - 1]) {
            response.statusCode = 500;
            response.setHeader('content-type', 'text/plain; charset=utf-8');
            response.end('Unable to resolve soapMethod from pathname');
            return;
        }

        let soapConfigName = pathArr.splice(0, pathArr.length - 1).join('/');
        let soapMethod = pathArr[pathArr.length - 1];
        let soapObj = soapCache[soapConfigName];
        if (!soapObj) {
            response.statusCode = 500;
            response.setHeader('content-type', 'text/plain; charset=utf-8');
            response.end(`Unable to resolve ${soapConfigName} from config`);
            return;
        }
        let bodyChunk = [];
        request.on('data', chunk => {
            bodyChunk.push(chunk);
        });
        request.on('end', () => {
            let body = JSON.parse(bodyChunk.toString());
            try {
                soapObj.request(body, soapMethod).then(resBody => {
                    request.resBody_len = JSON.stringify(resBody).length;
                    request.duration = Date.now() - request.startTime;
                    response.statusCode = 200;
                    response.setHeader('content-type', 'application/json; charset=utf-8');
                    response.end(Buffer.from(JSON.stringify(resBody)));
                }).catch(err => {
                    request.errMsg = err.toString();
                    request.duration = Date.now() - request.startTime;
                    response.statusCode = 500;
                    response.setHeader('content-type', 'text/html; charset=utf-8');
                    response.end(Buffer.from(err.toString()));
                });

            } catch (e) {
                request.errMsg = e.toString();
                response.statusCode = 500;
                response.setHeader('content-type', 'text/html; charset=utf-8');
                response.end(Buffer.from(e.toString()));
            }
        });
    } catch (e) {
        console.log(`request_error: ${nodeUtil.inspect(e)}`);
        response.statusCode = 502;
        response.end('ike httpToSoap proxy error');
    }
}

2.3、soapClient

发现一个soap的npm包,自动实现了编解码的各种任务,这里在它的基础之上再简单封装一层。

  1. start启动时,根据遍历的config配置文件依次完成SoapClient的初始化;
  2. 调用时,按需传递body和method,完成最终调用,给出响应即可;
  3. rawResponse,rawRequest都是原始的XML结构报文,可以自行拆解debug查看;
/**
 * create by ikejcwang on 2022.03.07.
 */
'use strict';
const soap = require('soap');
const soapOptions = {
    disableCache: true,
    escapeXML: false
}

/**
 * 构造自定义的soap类
 */
class SoapClient {

    constructor(config) {
        this.server = config.server;
        this.init()
    }

    init() {
        soap.createClientAsync(`${this.server}?wsdl`, soapOptions).then(client => {
            this.client = client
        }).catch(err => {
            this.err = err
        })
    }

    request(body, method) {
        return new Promise((resolve, reject) => {
            if (this.err) {
                reject(this.err)
            } else {
                if (!this.client[method]) {
                    reject(new Error('soap client does not support this method: ${method}'))
                } else {
                    this.client[method](body, (err, result, rawResponse, soapHeader, rawRequest) => {
                        if (err) {
                            reject(err)
                        } else {
                            resolve(result)
                        }
                    })
                }
            }
        })
    }
}

module.exports = SoapClient

2.4、调试

启动start.js,

wangjinchao@IKEJCWANG-MB0 ike_httpToSoap % curl http://127.0.0.1:8080/userManager/sayHelloWorldFrom -H "Content-Type:application/json" -d '{"arg0":"ikejcwang"}'
{"return":"Hello, world, from ikejcwang"}

其余函数的请求一样,http请求报文body就是对应函数参数的json数据,响应报文就是对应函数返回值的json数据。此处是一个简单的demo,只提供了一个基本数据类型参数。

2.5、结尾

原本RPC模式隐藏掉了网络传输的细节,是很少涉及黑白名单与签名鉴权之类的操作,因为server与client必须要约定好才能发起调用,即client必须是server认可的,否则,连server长什么模样都不知道。

但有了http server这层代理,我们就可以在请求soap服务的时候基于http server上面做类似的操作了,限流策略,防刷手段……只要是http链路可以实现的都支持。

(作者对于webservice不太熟,没有实际项目使用经验,只是现今有需要针对webservice的请求做协议转换的操作,才去看了下文档后,略知一二了)

目录
相关文章
|
8天前
|
数据采集 前端开发 安全
socks5代理是什么意思?它跟http代理有什么不同点?它有什么应用场景?
**SOCKS5**是一种代理协议,位于会话层,作为前端与服务器间的中介,确保通信安全。与**HTTP代理**不同,SOCKS5不处理应用层协议,提供更快的速度,且适用范围更广,不仅限于HTTP。主要应用场景包括数据采集和电子商务。选择使用取决于具体业务需求。
|
6天前
|
网络协议 安全 数据安全/隐私保护
Socks5代理和HTTP代理的区别在哪里?
Socks5和HTTP代理是两种IP代理方式,分别基于Socks5及HTTP协议。Socks5支持TCP/UDP,适合P2P、游戏和流媒体,提供传输层身份验证。HTTP代理仅支持HTTP,适用于Web浏览控制和内容过滤。选择代理应根据实际需求,如需高速低延迟选Socks5,需访问控制选HTTP。
|
8天前
|
缓存 监控 安全
HTTP代理,什么是HTTP代理?HTTP代理如何设置?HTTP代理的用途?
HTTP代理用于访问封锁网站、加速访问、提升安全,国内常用于网络加速。HTTP代理插件提供隐私保护、解除网站限制、增强安全及定制浏览体验。设置代理通常在操作系统网络设置或浏览器选项中进行,输入代理服务器地址和端口。
|
1天前
|
监控 安全 应用服务中间件
HTTPS代理搭建技巧分享​
请注意,HTTPS代理的配置和管理需要谨慎,特别是涉及SSL证书的处理。确保您的代理服务器按照最佳实践进行配置和管理,以提供安全可靠的代理服务。
7 3
|
6天前
|
网络协议 数据安全/隐私保护
Socks5代理和HTTP代理有哪些优缺点?
SOCKS5和HTTP代理对比:SOCKS5支持TCP/UDP,提供身份验证和数据加密,性能优但无法处理HTTP头部信息;HTTP代理能读取头部做细粒度控制,广泛支持但仅限HTTP协议,可能影响性能。选择取决于具体需求和应用支持。
|
8天前
|
安全 网络协议 网络安全
Socks5代理与HTTP代理的区别与优劣
在互联网时代,隐私和安全至关重要,代理服务器如socks5和HTTP代理成为保护个人信息的工具。socks5代理支持多种网络流量和UDP协议,更具灵活性和通用性,但设置复杂;HTTP代理则更常见、易用,适合仅处理HTTP流量的场景,但限制较多。选择哪种代理取决于具体需求和应用场景。
|
8天前
|
网络安全
socks5代理连接成功无法访问http协议的站点
无法通过SOCKS5代理访问HTTP网站可能由多个原因引起,如代理服务器不支持HTTP、配置错误、防火墙阻拦、连接问题、身份验证失败、浏览器设置不当或服务器被封锁。检查并解决这些因素,若问题持续,需深入排查或联系服务提供商。
|
9天前
|
数据采集
如何选择高质量http/Socks5代理ip?
选择高质量HTTP、Socks5代理IP对爬虫和数据采集至关重要。了解代理IP类型,根据需求选择HTTP或Socks5代理。选择稳定、快速、分布广泛、数量充足且不易被封的代理IP供应商。关注代理IP速度、质量、稳定性、区域和使用频率。测试代理IP的连接速度、可用性、匿名性和使用频率。定期更换代理IP以保持效率和可靠性。
|
10天前
|
Web App开发 缓存 安全
什么是HTTP代理?HTTP代理的作用?HTTP代理怎么设置?
HTTP代理是位于客户端和服务器之间的中间服务器,用于拦截并转发网络请求和响应。它能增强安全、缓存内容提升性能、访问受限资源。代理分为正向、反向和透明三种类型。设置HTTP代理涉及选择代理服务器,配置客户端的网络设置,或通过代理IP提供商获取服务。在Windows和macOS中,可在系统设置里配置代理;在Chrome和Firefox浏览器中,可通过浏览器设置进行代理配置。
|
10天前
|
监控 安全 API
在使用HTTP代理IP的需要注意什么?
在使用HTTP代理IP时,关键点包括选择可靠服务商、确保匿名性、合法合规使用、遵循服务商规定、定期更换IP、检测IP可用性、处理封禁、设置超时时间、监控服务商通知、使用加密协议、定期评估服务质量和遵守法规。这些措施能保证代理的有效性、安全性和合规性。