zoukankan      html  css  js  c++  java
  • 异步图片处理服务器

    基于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();
    }

    }

  • 相关阅读:
    java处理图片压缩、裁剪
    List按对象元素自定义排序
    List和数组汉字简单排序(转)
    欢迎访问我的个人博客,部分文章迁移
    Java资源免费分享,每日一更新,找到你心仪的吧
    今年的校招,Java好拿offer吗?
    【拥抱大厂系列】几个面试官常问的垃圾回收器,下次面试就拿这篇文章怼回去!
    2019,我的这一年,在校研究生做到年入20W,另送我的读者2000元现金红包
    深入理解Java虚拟机-如何利用VisualVM对高并发项目进行性能分析
    深入理解Java虚拟机-常用vm参数分析
  • 原文地址:https://www.cnblogs.com/zlfoak/p/4530077.html
Copyright © 2011-2022 走看看