异步图片处理服务器

基于netty实现的异步服务器。参见:

https://spring.io/guides/gs/reactor-thumbnailer/

package hello;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.event.Event;
import reactor.function.Function;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.awt.image.ImageObserver;
import java.nio.file.Files;
import java.nio.file.Path;

/**
* Uses the built-in JDK tooling for resizing an image.
*
* @author Jon Brisbin
*/
class BufferedImageThumbnailer implements Function<Event<Path>, Path> {

private static final ImageObserver DUMMY_OBSERVER = (img, infoflags, x, y, width, height) -> true;

private final Logger log = LoggerFactory.getLogger(getClass());

private final int maxLongSide;

public BufferedImageThumbnailer(int maxLongSide) {
this.maxLongSide = maxLongSide;
}

@Override
public Path apply(Event<Path> ev) {
try {
Path srcPath = ev.getData();
Path thumbnailPath = Files.createTempFile("thumbnail", ".jpg").toAbsolutePath();
BufferedImage imgIn = ImageIO.read(srcPath.toFile());

double scale;
if (imgIn.getWidth() >= imgIn.getHeight()) {
// horizontal or square image
scale = Math.min(maxLongSide, imgIn.getWidth()) / (double) imgIn.getWidth();
} else {
// vertical image
scale = Math.min(maxLongSide, imgIn.getHeight()) / (double) imgIn.getHeight();
}

BufferedImage thumbnailOut = new BufferedImage((int) (scale * imgIn.getWidth()),
(int) (scale * imgIn.getHeight()),
imgIn.getType());
Graphics2D g = thumbnailOut.createGraphics();

AffineTransform transform = AffineTransform.getScaleInstance(scale, scale);
g.drawImage(imgIn, transform, DUMMY_OBSERVER);
ImageIO.write(thumbnailOut, "jpeg", thumbnailPath.toFile());

log.info("Image thumbnail now at: {}", thumbnailPath);

return thumbnailPath;
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
}
}

}

REST接口

package hello;

import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.http.*;
import reactor.core.Reactor;
import reactor.event.Event;
import reactor.function.Consumer;
import reactor.net.NetChannel;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.atomic.AtomicReference;

import static io.netty.handler.codec.http.HttpHeaders.Names.*;
import static io.netty.handler.codec.http.HttpResponseStatus.*;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;

/**
* A helper class that contains the necessary Consumers for handling HTTP requests.
*/
public class ImageThumbnailerRestApi {

public static final String IMG_THUMBNAIL_URI = "/image/thumbnail.jpg";
public static final String THUMBNAIL_REQ_URI = "/thumbnail";

/**
* Accept an image upload via POST and notify a Reactor that the image needs to be thumbnailed. Asynchronously respond
* to the client when the thumbnailing has completed.
*
* @param channel
* the channel on which to send an HTTP response
* @param thumbnail
* a reference to the shared thumbnail path
* @param reactor
* the Reactor on which to publish events
*
* @return a consumer to handle HTTP requests
*/
public static Consumer<FullHttpRequest> thumbnailImage(NetChannel<FullHttpRequest, FullHttpResponse> channel,
AtomicReference<Path> thumbnail,
Reactor reactor) {
return req -> {
if (req.getMethod() != HttpMethod.POST) {
channel.send(badRequest(req.getMethod() + " not supported for this URI"));
return;
}

// write to a temp file
Path imgIn = null;
try {
imgIn = readUpload(req.content());
} catch (IOException e) {
throw new IllegalStateException(e.getMessage(), e);
}

// Asynchronously thumbnail the image to 250px on the long side
reactor.sendAndReceive("thumbnail", Event.wrap(imgIn), ev -> {
thumbnail.set(ev.getData());
channel.send(redirect());
});
};
}

/**
* Respond to GET requests and serve the thumbnailed image, a reference to which is kept in the given {@literal
* AtomicReference}.
*
* @param channel
* the channel on which to send an HTTP response
* @param thumbnail
* a reference to the shared thumbnail path
*
* @return a consumer to handle HTTP requests
*/
public static Consumer<FullHttpRequest> serveThumbnailImage(NetChannel<FullHttpRequest, FullHttpResponse> channel,
AtomicReference<Path> thumbnail) {
return req -> {
if (req.getMethod() != HttpMethod.GET) {
channel.send(badRequest(req.getMethod() + " not supported for this URI"));
} else {
try {
channel.send(serveImage(thumbnail.get()));
} catch (IOException e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
};
}

/**
* Respond to errors occurring on a Reactor by redirecting them to the client via an HTTP 500 error response.
*
* @param channel
* the channel on which to send an HTTP response
*
* @return a consumer to handle HTTP requests
*/
public static Consumer<Throwable> errorHandler(NetChannel<FullHttpRequest, FullHttpResponse> channel) {
return ev -> {
DefaultFullHttpResponse resp = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
HttpResponseStatus.INTERNAL_SERVER_ERROR);
resp.content().writeBytes(ev.getMessage().getBytes());
resp.headers().set(HttpHeaders.Names.CONTENT_TYPE, "text/plain");
resp.headers().set(HttpHeaders.Names.CONTENT_LENGTH, resp.content().readableBytes());
channel.send(resp);
};
}

////////////////////////// HELPER METHODS //////////////////////////
/*
* Read POST uploads and write them to a temp file, returning the Path to that file.
*/
private static Path readUpload(ByteBuf content) throws IOException {
byte[] bytes = new byte[content.readableBytes()];
content.readBytes(bytes);
content.release();

// write to a temp file
Path imgIn = Files.createTempFile("upload", ".jpg");
Files.write(imgIn, bytes);

imgIn.toFile().deleteOnExit();

return imgIn;
}

/*
* Create an HTTP 400 bad request response.
*/
public static FullHttpResponse badRequest(String msg) {
DefaultFullHttpResponse resp = new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST);
resp.content().writeBytes(msg.getBytes());
resp.headers().set(CONTENT_TYPE, "text/plain");
resp.headers().set(CONTENT_LENGTH, resp.content().readableBytes());
return resp;
}

/*
* Create an HTTP 301 redirect response.
*/
public static FullHttpResponse redirect() {
DefaultFullHttpResponse resp = new DefaultFullHttpResponse(HTTP_1_1, MOVED_PERMANENTLY);
resp.headers().set(CONTENT_LENGTH, 0);
resp.headers().set(LOCATION, IMG_THUMBNAIL_URI);
return resp;
}

/*
* Create an HTTP 200 response that contains the data of the thumbnailed image.
*/
public static FullHttpResponse serveImage(Path path) throws IOException {
DefaultFullHttpResponse resp = new DefaultFullHttpResponse(HTTP_1_1, OK);

RandomAccessFile f = new RandomAccessFile(path.toString(), "r");
resp.headers().set(CONTENT_TYPE, "image/jpeg");
resp.headers().set(CONTENT_LENGTH, f.length());

byte[] bytes = Files.readAllBytes(path);
resp.content().writeBytes(bytes);

return resp;
}

}

Netty服务器:

package hello;

import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import reactor.core.Environment;
import reactor.core.Reactor;
import reactor.core.composable.Stream;
import reactor.core.spec.Reactors;
import reactor.net.NetServer;
import reactor.net.config.ServerSocketOptions;
import reactor.net.netty.NettyServerSocketOptions;
import reactor.net.netty.tcp.NettyTcpServer;
import reactor.net.tcp.spec.TcpServerSpec;
import reactor.spring.context.config.EnableReactor;

import java.nio.file.Path;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;

import static reactor.event.selector.Selectors.$;

/**
* Simple Spring Boot app to start a Reactor+Netty-based REST API server for thumbnailing uploaded images.
*/
@EnableAutoConfiguration
@Configuration
@ComponentScan
@EnableReactor
public class ImageThumbnailerApp {

@Bean
public Reactor reactor(Environment env) {
Reactor reactor = Reactors.reactor(env, Environment.THREAD_POOL);

// Register our thumbnailer on the Reactor
reactor.receive($("thumbnail"), new BufferedImageThumbnailer(250));

return reactor;
}

@Bean
public ServerSocketOptions serverSocketOptions() {
return new NettyServerSocketOptions()
.pipelineConfigurer(pipeline -> pipeline.addLast(new HttpServerCodec())
.addLast(new HttpObjectAggregator(16 * 1024 * 1024)));
}

@Bean
public NetServer<FullHttpRequest, FullHttpResponse> restApi(Environment env,
ServerSocketOptions opts,
Reactor reactor,
CountDownLatch closeLatch) throws InterruptedException {
AtomicReference<Path> thumbnail = new AtomicReference<>();

NetServer<FullHttpRequest, FullHttpResponse> server = new TcpServerSpec<FullHttpRequest, FullHttpResponse>(
NettyTcpServer.class)
.env(env).dispatcher("sync").options(opts)
.consume(ch -> {
// filter requests by URI via the input Stream
Stream<FullHttpRequest> in = ch.in();

// serve image thumbnail to browser
in.filter((FullHttpRequest req) -> ImageThumbnailerRestApi.IMG_THUMBNAIL_URI.equals(req.getUri()))
.when(Throwable.class, ImageThumbnailerRestApi.errorHandler(ch))
.consume(ImageThumbnailerRestApi.serveThumbnailImage(ch, thumbnail));

// take uploaded data and thumbnail it
in.filter((FullHttpRequest req) -> ImageThumbnailerRestApi.THUMBNAIL_REQ_URI.equals(req.getUri()))
.when(Throwable.class, ImageThumbnailerRestApi.errorHandler(ch))
.consume(ImageThumbnailerRestApi.thumbnailImage(ch, thumbnail, reactor));

// shutdown this demo app
in.filter((FullHttpRequest req) -> "/shutdown".equals(req.getUri()))
.consume(req -> closeLatch.countDown());
})
.get();

server.start().await();

return server;
}

@Bean
public CountDownLatch closeLatch() {
return new CountDownLatch(1);
}

public static void main(String... args) throws InterruptedException {
ApplicationContext ctx = SpringApplication.run(ImageThumbnailerApp.class, args);

// Reactor‘s TCP servers are non-blocking so we have to do something to keep from exiting the main thread
CountDownLatch closeLatch = ctx.getBean(CountDownLatch.class);
closeLatch.await();
}

}

时间: 2024-11-05 15:55:40

异步图片处理服务器的相关文章

iOS开发&gt;学无止境 - 异步图片加载优化与常用开源库分析

作者:罗轩(@luoyibu) 网址:http://www.jianshu.com/p/3b2c95e1404f 1. 网络图片显示大体步骤:   下载图片 图片处理(裁剪,边框等) 写入磁盘 从磁盘读取数据到内核缓冲区 从内核缓冲区复制到用户空间(内存级别拷贝) 解压缩为位图(耗cpu较高) 如果位图数据不是字节对齐的,CoreAnimation会copy一份位图数据并进行字节对齐 CoreAnimation渲染解压缩过的位图 以上4,5,6,7,8步是在UIImageView的setImag

简单的图片裁剪服务器

自己写的一个简单的图片服务器,可以读取FastDFS上的图片,根据参数进行图片裁剪输出到前台 改项目可以上传图片到FastDFS,读取FastDFS上存储的图片,前面可以增加Varnish图片缓存服务器缓解图片裁剪压力 使用一个简单的Servlet实现 package com.imgcut.servlet; import java.io.IOException; import java.io.InputStream; import javax.servlet.ServletException;

c#异步Socket Tcp服务器实现

原创性申明 本文作者: 小竹zz  本文地址:http://blog.csdn.net/zhujunxxxxx 转载请注明出处. 介绍 我之前写过一篇IOCP的文章: http://blog.csdn.net/zhujunxxxxx/article/details/43573879 这个比异步socket性能好,因为它复用对象了. 在c#中微软已经提供了TcpListener和TcpClient来实现Tcp的通讯,这部分已经有人写了比较好的异步服务器代码 http://www.cnblogs.c

Ajax+PHP实现异步图片上传

1.html <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Ajax+PHP实现异步图片上传</title> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> <s

客户端程序传送图片到服务器

转载:http://www.cnblogs.com/networkcomms/p/4314898.html 源码 (因为空间大小限制,不包含通信框架源码,通信框架源码请另行下载) 以前帮朋友做了一个图片采集系统,客户端采集相片后,通过TCP通信传送到服务器,本文把客户端传送图片到服务器的这部分提取出来. 由于每张图片的大小都不大,所以我们在传输图片时,没有采用传送文件的方式,而是采用了直接序列化图片的方式来进行. 当前支持的图片类型: jpg,png,gif 您可以自己添加扩充支持的图片类型 通

用nginx图片缓存服务器

用nginx图片缓存服务器 图片的存储硬件 把图片存储到什么介质上? 如果有足够的资金购买专用的图片服务器硬件或者 NAS 设备,那么简单的很: 如果上述条件不具备,只想在普通的硬盘上存储,首先还是要考虑一下物理硬盘的实际处理能力.是 7200 转的还是 15000 转的,实际表现差别就很大.是选择 ReiserFS 还是 Ext3 ,怎么也要测试一下吧? 创建文件系统的时候 Inode 问题也要加以考虑,选择合适大小的 inode size ,在空间和速度上做取舍,同时防患于未然,注意单个文件

集合差集 哈希表 比较数据库中的图片和服务器上的图片,将服务器上的垃圾图片删除

SSH 框架下code: public String deleRubbishAd(){ int deleADcount = 0; rubbishADtp = configDao.rubbishADtp(); //数据库中的广告图片集合 Map<Object,Object> shujuku= new HashMap<Object,Object>(); File adfile = new File("C://Program Files//Apache Software Fou

[c#源码分享]客户端程序传送图片到服务器

源码 (因为空间大小限制,不包含通信框架源码,通信框架源码请另行下载) 以前帮朋友做了一个图片采集系统,客户端采集相片后,通过TCP通信传送到服务器,本文把客户端传送图片到服务器的这部分提取出来. 由于每张图片的大小都不大,所以我们在传输图片时,没有采用传送文件的方式,而是采用了直接序列化图片的方式来进行. 当前支持的图片类型: jpg,png,gif 您可以自己添加扩充支持的图片类型 通信框架采用英国的开源的networkcomms2.3.1 通信框架   序列化器采用开源的protobuf.

iOS 异步图片加载优化与常用开源库分析

1. 网络图片显示大体步骤: 下载图片 图片处理(裁剪,边框等) 写入磁盘 从磁盘读取数据到内核缓冲区 从内核缓冲区复制到用户空间(内存级别拷贝) 解压缩为位图(耗cpu较高) 如果位图数据不是字节对齐的,CoreAnimation会copy一份位图数据并进行字节对齐 CoreAnimation渲染解压缩过的位图 以上4,5,6,7,8步是在UIImageView的setImage时进行的,所以默认在主线程进行(iOS UI操作必须在主线程执行). 2. 一些优化思路: 异步下载图片 image