api-gateway实践(19)InputStream的复用

相关链接:

inputstream复制:http://www.cnblogs.com/happyaday/p/4616023.html

对象克隆:https://zhidao.baidu.com/question/181598914089683044.html

一、问题提出

在进行网关引擎开发时,获取到一个http请求的inputstream后,可能要多次利用它进行read操作。由于流读过一次就不能再读了,所以需要实现InputStream的复制。

而InputStream对象本身不能复制,因为它没有实现Cloneable接口。

二、解决方案

1、方案一:使用ByteArrayOutputStream<->InputStream

此时,可以先把InputStream转化成ByteArrayOutputStream,后面要使用InputStream对象时,再从ByteArrayOutputStream转化回来就好了。代码实现如下:

InputStream input = httpconn.getInputStream();

ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = input.read(buffer)) > -1 ) {
    baos.write(buffer, 0, len);
}
baos.flush();

InputStream stream1 = new ByteArrayInputStream(baos.toByteArray());  //TODO:显示到前台
InputStream stream2 = new ByteArrayInputStream(baos.toByteArray());   //TODO:本地缓存

这种适用于一些不是很大的流,因为缓存流是会消耗内存的。

2、方案二:使用流的mark 和 reset方法

InputStream本身提供了三个接口:

第一个,InputStream是否支持mark,默认不支持。

public boolean markSupported() {
    return false;
}

public boolean markSupported() {
    return false;
}

第二个,mark接口。该接口在InputStream中默认实现不做任何事情。

public synchronized void mark(int readlimit) {}
public synchronized void mark(int readlimit) {}

第三个,reset接口。该接口在InputStream中实现,调用就会抛异常。

public synchronized void reset() throws IOException {
    throw new IOException("mark/reset not supported");
}
public synchronized void reset() throws IOException {
    throw new IOException("mark/reset not supported");
}

从三个接口定义中可以看出,首先InputStream默认是不支持mark的,子类需要支持mark必须重写这三个方法。

第一个接口很简单,就是标明该InputStream是否支持mark。

mark接口的官方文档解释: “在此输入流中标记当前的位置。对 reset 方法的后续调用会在最后标记的位置重新定位此流,以便后续读取重新读取相同的字节。 readlimit 参数告知此输入流在标记位置失效之前允许读取许多字节。
mark 的常规协定是:如果方法 markSupported 返回 true,则输入流总会在调用 mark 之后记住所有读取的字节,并且无论何时调用方法 reset ,都会准备再次提供那些相同的字节。但是,如果在调用 reset 之前可以从流中读取多于 readlimit 的字节,则根本不需要该流记住任何数据。”

reset接口的官方文档解释: 将此流重新定位到对此输入流最后调用 mark 方法时的位置。 reset 的常规协定是:
如果方法 markSupported 返回 true,则: 如果创建流以来未调用方法 mark,或最后调用 mark 以来从该流读取的字节数大于最后调用 mark 时的参数,则可能抛出 IOException。 如果未抛出这样的 IOException,则将该流重新设置为这种状态:最近调用 mark 以来(或如果未调用 mark,则从文件开始以来)读取的所有字节将重新提供给 read 方法的后续调用方,后接可能是调用 reset 时的下一输入数据的所有字节。 如果方法 markSupported 返回 false,则: 对 reset 的调用可能抛出 IOException。 如果未抛出 IOException,则将该流重新设置为一种固定状态,该状态取决于输入流的特定类型和其创建方式的固定状态。提供给 read 方法的后续调用方的字节取决于特定类型的输入流。
简而言之就是: 调用mark方法会记下当前调用mark方法的时刻,InputStream被读到的位置。 调用reset方法就会回到该位置。

举个简单的例子:
String content = "BoyceZhang!";
InputStream inputStream = new ByteArrayInputStream(content.getBytes());

// 判断该输入流是否支持mark操作
if (!inputStream.markSupported()) {
    System.out.println("mark/reset not supported!");
}

int ch;
boolean marked = false;
while ((ch = inputStream.read()) != -1) {

//读取一个字符输出一个字符
    System.out.print((char)ch);
    //读到 ‘e‘的时候标记一下
    if (((char)ch == ‘e‘)& !marked) {
        inputStream.mark(content.length()); //先不要理会mark的参数
        marked = true;
    }

//读到‘!‘的时候重新回到标记位置开始读
    if ((char)ch == ‘!‘ && marked) {
        inputStream.reset();
        marked = false;
    }
}

//程序最终输出:BoyceZhang!Zhang!

看了这个例子之后对mark和reset接口有了很直观的认识。 但是mark接口的参数readlimit究竟是干嘛的呢?

我们知道InputStream是不支持mark的。要想支持mark子类必须重写这三个方法,我想说的是不同的实现子类,mark的参数readlimit作用不尽相同。常用的FileInputStream不支持mark。

1. 对于BufferedInputStream,readlimit表示:InputStream调用mark方法的时刻起,在读取readlimit个字节之前,标记的该位置是有效的。如果读取的字节数大于readlimit,可能标记的位置会失效。

在BufferedInputStream的read方法源码中有这么一段:
} else if (buffer.length >= marklimit) {
markpos = -1; /* buffer got too big, invalidate mark */
pos = 0; /* drop buffer contents */
} else { /* grow buffer */
} else if (buffer.length >= marklimit) {
markpos = -1; /* buffer got too big, invalidate mark */
pos = 0; /* drop buffer contents */
} else { /* grow buffer */

为什么是可能会失效呢? 因为BufferedInputStream读取不是一个字节一个字节读取的,是一个字节数组一个字节数组读取的。

例如,readlimit=35,第一次比较的时候buffer.length=0(没开始读)<readlimit 然后buffer数组一次读取48个字节。这时的read方法只会简单的挨个返回buffer数组中的字节,不会做这次比较。直到读到buffer数组最后一个字节(第48个)后,才重新再次比较。这时如果我们读到buffer中第47个字节就reset。mark仍然是有效的。虽然47>35。

2. 对于InputStream的另外一个实现类:ByteArrayInputStream,我们发现readlimit参数根本就没有用,调用mark方法的时候写多少都无所谓。
public void mark(int readAheadLimit) {
mark = pos;
}

public synchronized void reset() {
pos = mark;
}
public void mark(int readAheadLimit) {
mark = pos;
}

public synchronized void reset() {
pos = mark;
}
因为对于ByteArrayInputStream来说,都是通过字节数组创建的,内部本身就保存了整个字节数组,mark只是标记一下数组下标位置,根本不用担心mark会创建太大的buffer字节数组缓存。

3. 其他的InputStream子类没有去总结。原理都是一样的。

所以由于mark和reset方法配合可以记录并回到我们标记的流的位置重新读流,很大一部分就可以解决我们的某些重复读的需要。

这种方式的优点很明显:不用缓存整个InputStream数据。对于ByteArrayInputStream甚至没有任何的内存开销。

当然这种方式也有缺点:就是需要通过干扰InputStream的读取细节,也相对比较复杂。

时间: 2024-10-16 21:28:05

api-gateway实践(19)InputStream的复用的相关文章

孢子框架-接口访问层、ESB、微服务API GateWay对比

如果从百度去搜索“接口访问层”你会发现主要是.NET里面的技术,叫做IDAL,其实是数据访问层接口.它的主要作用是兼容多种数据库.比如你定义一个标准接口,然后实现改接口的SqlServer访问和Oracle访问,那么利用IDAL就可以自由切换数据库.看.NET DEMO PetShop4,总共有22个项目.大体思想是3层,从Model.DAL.BLL,然后他在各层上又采用了工厂模式,把逻辑与实现想分离,比如以前BLL直接调用DAL就好了,但现在BLL却调用了IDAL,IDAL就是一个接口层,里面

谈谈微服务中的 API 网关(API Gateway)

转载至:http://www.cnblogs.com/savorboard/p/api-gateway.html 背景 我们知道在微服务架构风格中,一个大应用被拆分成为了多个小的服务系统提供出来,这些小的系统他们可以自成体系,也就是说这些小系统可以拥有自己的数据库,框架甚至语言等,这些小系统通常以提供 Rest Api 风格的接口来被 H5, Android, IOS 以及第三方应用程序调用. 但是在UI上进行展示的时候,我们通常需要在一个界面上展示很多数据,这些数据可能来自于不同的微服务中,举

.net core 微服务之Api网关(Api Gateway)

原文:.net core 微服务之Api网关(Api Gateway) 微服务网关目录 1. 微服务引子 2.使用Nginx作为api网关 3.自创api网关(重复轮子) 3.1.构建初始化 3.2.构建中间件 4.结语 引用链接 1. 微服务引子 首先恭喜你,进入微服务的开发世界.微服务属于架构演进中的一种阶段,其特点是根据业务模块水平划分服务种类,每个服务可以独立部署并互相隔离,并对外提供轻量的Api调用,服务具有高可用特性. 微服务应遵循的设计原则: 单一职责原则: 每个微服务只需要实现自

基于aws api gateway的asp.net core验证

本文是介绍aws 作为api gateway,用asp.net core用web应用,.net core作为aws lambda function. api gateway和asp.net core的用处不废话,直接上操作步骤. 首先在asw的凭据管理中添加操作的用户和角色,步骤如下: 注意选择的策略名称 下载csv备用 安装aws的visual studio插件 加载备用csv文件 创建asw lambda funcation项目 代码如下: 1 using System; 2 3 using

gRPC helloworld service, RESTful JSON API gateway and swagger UI

概述 本篇博文完整讲述了如果通过 protocol buffers 定义并启动一个 gRPC 服务,然后在 gRPC 服务上提供一个 RESTful JSON API 的反向代理 gateway,最后通过 swagger ui 来提供 RESTful JSON API 的说明,完整代码 helloworld_restful_swagger. Helloworld gRPC Service 参考 gRPC Quick Start for Python. Install gRPC 安装 gRPC 运

[译]API网关(API Gateway)

This a translation of an article ( http://microservices.io/patterns/apigateway.html) originally written and copyrighted by Chris Richardson ( http://twitter.com/crichardson). 模式:API网关 背景 我们假设你使用微服务模式创建一个在线商店,并正在实现商品详情页面.你需要开发多个版本的商品详情用户界面: 用于桌面和手机浏览器

微服务API Gateway

翻译-微服务API Gateway 原文地址:http://microservices.io/patterns/apigateway.html,以下是使用google翻译对原文的翻译. 让我们想象一下你正在建立一个使用微服务模式的网上商店,你所用的产品详细信息页面.你需要开发多个版本的产品详情界面: l  由服务器端Web应用程序生成的HTML - HTML5/ JavaScript的桌面和移动浏览器用户界面. l  原生Android和iPhone客户端 - 这些客户端通过的REST API服

[转载] 构建微服务:使用API Gateway

原文: http://mp.weixin.qq.com/s?__biz=MzA5OTAyNzQ2OA==&mid=206889381&idx=1&sn=478ccb35294c58d25d2df2d9ced65cf7&scene=1&key=c76941211a49ab586d79043cb87ac0dfeede574a20b2208ce76058b151624e4273182de582a786668ea347c6f317b389&ascene=0&

[AWS Lambda] Use AWS Lambda and API Gateway to return data

In this lesson, you will learn how to create a simple AWS Lambda function to submit a name via an API Gateway and return a resume for that person. At the end of the lesson, you will be able to create a Lambda function, and API Gateway, and understand

微服务实战(二):使用API Gateway

[编者的话]本系列的第一篇介绍了微服务架构模式.它讨论了采用微服务的优点和缺点,除了一些复杂的微服务,这种模式还是复杂应用的理想选择. 当你决定将应用作为一组微服务时,需要决定应用客户端如何与微服务交互.在单体式程序中,通常只有一组冗余的或者负载均衡的服务提供点.在微服务架构中,每一个微服务暴露一组细粒度的服务提供点.在本篇文章中,我们来看它如何影响客户端到服务端通信,同时提出一种API Gateway的方法. 介绍 假定你正在为在线购物应用开发一个原生手机客户端.你需要实现一个产品最终页来展示