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的请求做协议转换的操作,才去看了下文档后,略知一二了)

目录
相关文章
|
4月前
|
安全 网络协议 网络安全
IP代理的三大协议:HTTP、HTTPS与SOCKS5的区别
**HTTP代理**适用于基本网页浏览,简单但不安全;**HTTPS代理**提供加密,适合保护隐私;**SOCKS5代理**灵活强大,支持TCP/UDP及认证,适用于绕过限制。选择代理协议应考虑安全、效率及匿名需求。
|
4月前
|
Web App开发 缓存 JavaScript
使用TypeScript创建高效HTTP代理请求
使用TypeScript创建高效HTTP代理请求
|
2月前
|
缓存 负载均衡 安全
|
1月前
|
网络协议 物联网 网络性能优化
物联网江湖风云变幻!MQTT CoAP RESTful/HTTP XMPP四大门派谁主沉浮?
【9月更文挑战第3天】物联网(IoT)的兴起催生了多种通信协议,如MQTT、CoAP、RESTful/HTTP和XMPP,各自适用于不同场景。本文将对比这些协议的特点、优缺点,并提供示例代码。MQTT轻量级且支持QoS,适合大规模部署;CoAP基于UDP,适用于低功耗网络;RESTful/HTTP易于集成但不适合资源受限设备;XMPP支持双向通信,适合复杂交互应用。通过本文,开发者可更好地选择合适的物联网通信协议。
34 2
|
2月前
|
安全 Java Shell
【内网—内网转发】——http协议代理转发_reGeorg代理转发
【内网—内网转发】——http协议代理转发_reGeorg代理转发
34 3
|
3月前
|
数据采集 缓存 安全
2024年最佳http 代理 IP选择及其价格分析
2024年,多家服务商如快代理、123Proxy、巨量代理、IPIDEA等提供不同类型的代理IP,以满足数据采集、跨境电商等多种需求。
2024年最佳http 代理 IP选择及其价格分析
|
2月前
|
网络协议 物联网 网络性能优化
物联网江湖风云变幻!MQTT CoAP RESTful/HTTP XMPP四大门派谁主沉浮?
【8月更文挑战第14天】本文概览了MQTT、CoAP、RESTful/HTTP及XMPP四种物联网通信协议。MQTT采用发布/订阅模式,轻量高效;CoAP针对资源受限设备,基于UDP,低延迟;RESTful/HTTP易于集成现有Web基础设施;XMPP支持双向通信,扩展性强。每种协议均附有示例代码,助您根据不同场景和设备特性作出最佳选择。
34 5
|
3月前
|
数据采集 缓存 负载均衡
实测 | 芝麻代理,快代理、熊猫代理、豌豆代理HTTP代理质量测试
哈喽大家,欢迎来到本期知识分享!我们将探讨HTTP代理的质量分析方法,无论新手还是资深用户都能从中受益。首先介绍了HTTP代理的基本概念及其重要性。接着,我们通过两个关键指标——响应时间和可用性来评估代理质量。响应时间可通过`curl`命令测试并计算平均值;可用性则需设置定时任务持续检测,比如使用Python脚本。最后,通过具体案例分析了几家知名代理供应商的表现,其中青果网络在各项指标上表现突出,是进行数据采集等活动的优质选择。记得选择最适合自己的代理服务哦!
实测 | 芝麻代理,快代理、熊猫代理、豌豆代理HTTP代理质量测试
|
3月前
|
消息中间件 API 数据库
在微服务架构中,每个服务通常都是一个独立运行、独立部署、独立扩展的组件,它们之间通过轻量级的通信机制(如HTTP/RESTful API、gRPC等)进行通信。
在微服务架构中,每个服务通常都是一个独立运行、独立部署、独立扩展的组件,它们之间通过轻量级的通信机制(如HTTP/RESTful API、gRPC等)进行通信。
|
4月前
|
数据采集 安全 大数据
http代理一般受众于哪些人群?
HTTP代理主要适用于三类人群:数据采集专业人士,如网络爬虫开发者;网络兼职者,例如游戏试玩、电商优化者,利用代理IP提高工作效率;以及网络推广者,借助代理发布广告帖子以提升品牌知名度。代理提供安全、效率和稳定性支持。
77 3
http代理一般受众于哪些人群?