netty服务端处理请求联合pipeline分析

目录
  • 两个问题
  • NioMessageUnsafe.read()
  • ServerBootstrap.init(Channel channel)
    • ChannelInitializer的继承关系
      • PendingHandlerAddedTask构造方法
      • PendingHandlerCallback构造方法
      • 回到callHandlerCallbackLater方法
  • AbstractChannel.register0(ChannelPromise promise)
    • pipeline.invokeHandlerAddedIfNeeded()
      • DefaultChannelPipeline.callHandlerAddedForAllHandlers
      • 进入PendingHandlerAddedTask.execute()
      • callHandlerAdded0(final AbstractChannelHandlerContext ctx)
  • 再回到ServerBootstrap.init(Channel channel)
    • ChannelInitializer.handlerAdded(ChannelHandlerContext ctx)
      • ChannelInitializer.initChannel(ChannelHandlerContext ctx)
      • 继续跟进initChannel方法
  • 看ServerBootstrapAcceptor的channelRead方法
  • 总结

两个问题

  • 在客户端接入的时候, NioMessageUnsaferead方法中pipeline.fireChannelRead(readBuf.get(i))为什么会调用到ServerBootstrap的内部类ServerBootstrapAcceptor中的channelRead()方法。
  • 客户端handler是什么时候被添加的?

先分析第一个问题。回到netty处理客户端请求分析_1中服务端接收到accpet事件后,进行读取的方法NioMessageUnsafe.read()

NioMessageUnsafe.read()

public void read() {
    //必须是NioEventLoop方法调用的, 不能通过外部线程调用
    assert eventLoop().inEventLoop();
    //服务端channel的config
    final ChannelConfig config = config();
    //服务端channel的pipeline
    final ChannelPipeline pipeline = pipeline();
    //处理服务端接入的速率
    final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
    //设置配置
    allocHandle.reset(config);
    boolean closed = false;
    Throwable exception = null;
    try {
        try {
            do {
                //创建jdk底层的channel
                //readBuf用于临时承载读到链接
                int localRead = doReadMessages(readBuf);
                if (localRead == 0) {
                    break;
                }
                if (localRead < 0) {
                    closed = true;
                    break;
                }
                //分配器将读到的链接进行计数
                allocHandle.incMessagesRead(localRead);
                //连接数是否超过最大值
            } while (allocHandle.continueReading());
        } catch (Throwable t) {
            exception = t;
        }
        int size = readBuf.size();
        //遍历每一条客户端连接
        for (int i = 0; i < size; i ++) {
            readPending = false;
            //传递事件, 将创建NioSokectChannel进行传递
            //最终会调用ServerBootstrap的内部类ServerBootstrapAcceptor的channelRead()方法
            pipeline.fireChannelRead(readBuf.get(i));
        }
        readBuf.clear();
        allocHandle.readComplete();
        pipeline.fireChannelReadComplete();
        //代码省略
    } finally {
        //代码省略
    }
}

重点看pipeline.fireChannelRead(readBuf.get(i))

首先, 这里pipeline是服务端channelpipeline, 也就是NioServerSocketChannelpipeline

我们学习过pipeline之后, 对这种写法并不陌生, 就是传递channelRead事件, 这里通过传递channelRead事件走到了ServerBootstrapAcceptorchannelRead()方法, 说明在这步之前, ServerBootstrapAcceptor作为一个handler添加到了服务端channelpipeline中, 那么这个handler什么时候添加的呢?

我们回顾下第一章, 初始化NioServerSocketChannel的时候, 调用了ServerBootstrap的init方法 回顾下ServerBootstrap.init的调用链路:

ServerBootstrap.bind(8899) ---> AbstractBootstrap.doBind(final SocketAddress localAddress) ---> AbstractBootstrap.initAndRegister() ---> ServerBootstrap.init(Channel channel)

ServerBootstrap.init(Channel channel)

void init(Channel channel) throws Exception {
    //获取用户定义的选项(1)
    final Map<ChannelOption<?>, Object> options = options0();
    synchronized (options) {
        channel.config().setOptions(options);
    }
    //获取用户定义的属性(2)
    final Map<AttributeKey<?>, Object> attrs = attrs0();
    synchronized (attrs) {
        for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) {
            @SuppressWarnings("unchecked")
            AttributeKey<Object> key = (AttributeKey<Object>) e.getKey();
            channel.attr(key).set(e.getValue());
        }
    }
    //获取channel的pipline(3)
    ChannelPipeline p = channel.pipeline();
    //work线程组(4)
    final EventLoopGroup currentChildGroup = childGroup;
    //用户设置的Handler(5)
    final ChannelHandler currentChildHandler = childHandler;
    final Entry<ChannelOption<?>, Object>[] currentChildOptions;
    final Entry<AttributeKey<?>, Object>[] currentChildAttrs;
    //选项转化为Entry对象(6)
    synchronized (childOptions) {
        currentChildOptions = childOptions.entrySet().toArray(newOptionArray(childOptions.size()));
    }
    //属性转化为Entry对象(7)
    synchronized (childAttrs) {
        currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(childAttrs.size()));
    }
    //添加服务端handler(8)
    p.addLast(new ChannelInitializer<Channel>() {
        //初始化channel
        @Override
        public void initChannel(Channel ch) throws Exception {
            final ChannelPipeline pipeline = ch.pipeline();
            ChannelHandler handler = config.handler();
            if (handler != null) {
                pipeline.addLast(handler);
            }
            ch.eventLoop().execute(new Runnable() {
                @Override
                public void run() {
                    pipeline.addLast(new ServerBootstrapAcceptor(
                            currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                }
            });
        }
    });
}

我们重点关注第8步, 添加服务端channel, 这里的pipeline, 是服务服务端channelpipeline, 也就是NioServerSocketChannel绑定的pipeline, 这里添加了一个ChannelInitializer类型的handler

ChannelInitializer的继承关系

public abstract class ChannelInitializer&lt;C extends Channel&gt; extends ChannelInboundHandlerAdapter {
    //省略类体
}

我们看到其继承了ChannelInboundHandlerAdapter, 说明是一个inbound类型的handler

这里我们可能会想到, 添加完handler会执行handlerAdded, 然后在handlerAdded方法中做了添加ServerBootstrapAcceptor这个handler

但是, 实际上并不是这样的, 当程序执行到这里, 并没有马上执行handlerAdded, 我们紧跟addLast方法

最后执行到DefualtChannelPipeline.addLast(EventExecutorGroup group, String name, ChannelHandler handler)

public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
    final AbstractChannelHandlerContext newCtx;
    synchronized (this) {
        //判断handler是否被重复添加(1)
        checkMultiplicity(handler);
        //创建一个HandlerContext并添加到列表(2)
        newCtx = newContext(group, filterName(name, handler), handler);
        //添加HandlerContext(3)
        addLast0(newCtx);
        //是否已注册
        if (!registered) {
            newCtx.setAddPending();
            callHandlerCallbackLater(newCtx, true);
            return this;
        }
        EventExecutor executor = newCtx.executor();
        if (!executor.inEventLoop()) {
            newCtx.setAddPending();
            //回调用户事件
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    callHandlerAdded0(newCtx);
                }
            });
            return this;
        }
    }
    //回调添加事件(4)
    callHandlerAdded0(newCtx);
    return this;
}

首先完成了handler的添加, 但是并没有马上执行回调

这里我们重点关注if (!registered)这个条件判断, 其实在注册完成, registered会变成true, 但是走到这一步的时候NioServerSockeChannel并没有完成注册(可以回顾第一章看注册在哪一步), 所以会进到if里并返回自身

DefualtChannelPipeline.callHandlerCallbackLater(AbstractChannelHandlerContext ctx, boolean added)

private void callHandlerCallbackLater(AbstractChannelHandlerContext ctx, boolean added) {
    assert !registered;
    //判断是否已添加, 未添加, 进行添加, 已添加进行删除
    PendingHandlerCallback task = added ? new PendingHandlerAddedTask(ctx) : new PendingHandlerRemovedTask(ctx);
    //获取第一个Callback任务
    PendingHandlerCallback pending = pendingHandlerCallbackHead;
    //如果第一个Callback任务为空
    if (pending == null) {
        //将第一个任务设置为刚创建的任务
        pendingHandlerCallbackHead = task;
    } else {
        while (pending.next != null) {
            pending = pending.next;
        }
        pending.next = task;
    }
}

因我们调用这个方法的时候added传的true, 所以PendingHandlerCallback task赋值为new PendingHandlerAddedTask(ctx)

PendingHandlerAddedTask这个类, 我们从名字可以看出, 这是一个handler添加的延迟任务, 用于执行handler延迟添加的操作, 同样也对应一个名字为PendingHandlerRemovedTask的类, 用于执行延迟删除handler的操作, 这两个类都继承抽象类PendingHandlerCallback

PendingHandlerAddedTask构造方法

PendingHandlerAddedTask(AbstractChannelHandlerContext ctx) {
    super(ctx);
}

进入super(ctx)

PendingHandlerCallback构造方法

PendingHandlerCallback(AbstractChannelHandlerContext ctx) {
    this.ctx = ctx;
}

在父类中, 保存了要添加的context, 也就是ChannelInitializer类型的包装类

回到callHandlerCallbackLater方法

PendingHandlerCallback pending = pendingHandlerCallbackHead;

这表示获取第一个PendingHandlerCallback的任务, 其实PendingHandlerCallback是一个单向链表, 自身维护一个PendingHandlerCallback类型的next, 指向下一个任务, 在DefaultChannelPipeline这个类中, 定义了个PendingHandlerCallback类型的引用pendingHandlerCallbackHead, 用来指向延迟回调任务的中的第一个任务。

之后判断这个任务是为空, 如果是第一次添加handler, 那么这里就是空, 所以将第一个任务赋值为我们刚创建的添加任务。

如果不是第一次添加handler, 则将我们新创建的任务添加到链表的尾部, 因为这里我们是第一次添加, 所以第一个回调任务就指向了我们创建的添加handler的任务。

完成这一系列操作之后, addLast方法返归, 此时并没有完成添加操作。

而什么时候完成添加操作的呢?

回到在服务端channel注册时候的会走到AbstractChannel.register0方法 回顾下AbstractChannel.register0的调用链路:

ServerBootstrap.bind(8899) ---> AbstractBootstrap.doBind(final SocketAddress localAddress) ---> AbstractBootstrap.initAndRegister() ---> config().group().register(channel) ---> SingleThreadEventLoop.register(final ChannelPromise promise) ---> AbstractChannel.register(EventLoop eventLoop, final ChannelPromise promise) ---> AbstractChannel.register0(ChannelPromise promise)

AbstractChannel.register0(ChannelPromise promise)

private void register0(ChannelPromise promise) {
    try {
        //做实际的注册(1)
        doRegister();
        neverRegistered = false;
        registered = true;
        //触发事件(2)
        pipeline.invokeHandlerAddedIfNeeded();
        safeSetSuccess(promise);
        //触发注册成功事件(3)
        pipeline.fireChannelRegistered();
        if (isActive()) {
            if (firstRegistration) {
                //传播active事件(4)
                pipeline.fireChannelActive();
            } else if (config().isAutoRead()) {
                beginRead();
            }
        }
    } catch (Throwable t) {
        //省略代码
    }
}

重点关注第二步pipeline.invokeHandlerAddedIfNeeded(), 这里已经通过doRegister()方法完成了实际的注册, 我们跟到该方法中

pipeline.invokeHandlerAddedIfNeeded()

final void invokeHandlerAddedIfNeeded() {
    assert channel.eventLoop().inEventLoop();
    if (firstRegistration) {
        firstRegistration = false;
        callHandlerAddedForAllHandlers();
    }
}

这里会判断是否第一次注册, 这里返回true, 然后会执行callHandlerAddedForAllHandlers()方法, 我们跟进去

DefaultChannelPipeline.callHandlerAddedForAllHandlers

private void callHandlerAddedForAllHandlers() {
    final PendingHandlerCallback pendingHandlerCallbackHead;
    synchronized (this) {
        assert !registered;
        registered = true;
        pendingHandlerCallbackHead = this.pendingHandlerCallbackHead;
        this.pendingHandlerCallbackHead = null;
    }
    //获取task
    PendingHandlerCallback task = pendingHandlerCallbackHead;
    while (task != null) {
        //执行添加handler方法
        task.execute();
        task = task.next;
    }
}

这里拿到第一个延迟执行handler添加的task其实就是我们之前剖析过的, 延迟执行handler添加的task, 就是PendingHandlerAddedTask对象

while循环中, 通过执行execute()方法将handler添加

进入PendingHandlerAddedTask.execute()

void execute() {
    //获取当前eventLoop线程
    EventExecutor executor = ctx.executor();
    //是当前执行的线程
    if (executor.inEventLoop()) {
        callHandlerAdded0(ctx);
    } else {
        try {
            //添加到队列
            executor.execute(this);
        } catch (RejectedExecutionException e) {
            //代码省略
        }
    }
}

再进入callHandlerAdded0方法

callHandlerAdded0(final AbstractChannelHandlerContext ctx)

private void callHandlerAdded0(final AbstractChannelHandlerContext ctx) {
    try {
        ctx.handler().handlerAdded(ctx);
        ctx.setAddComplete();
    } catch (Throwable t) {
        //省略...
    }
}

终于在这里, 我们看到了执行回调的方法

再回到ServerBootstrap.init(Channel channel)

void init(Channel channel) throws Exception {
    //获取用户定义的选项(1)
    final Map&lt;ChannelOption&lt;?&gt;, Object&gt; options = options0();
    synchronized (options) {
        channel.config().setOptions(options);
    }
    //获取用户定义的属性(2)
    final Map&lt;AttributeKey&lt;?&gt;, Object&gt; attrs = attrs0();
    synchronized (attrs) {
        for (Entry&lt;AttributeKey&lt;?&gt;, Object&gt; e: attrs.entrySet()) {
            @SuppressWarnings("unchecked")
            AttributeKey&lt;Object&gt; key = (AttributeKey&lt;Object&gt;) e.getKey();
            channel.attr(key).set(e.getValue());
        }
    }
    //获取channel的pipline(3)
    ChannelPipeline p = channel.pipeline();
    //work线程组(4)
    final EventLoopGroup currentChildGroup = childGroup;
    //用户设置的Handler(5)
    final ChannelHandler currentChildHandler = childHandler;
    final Entry&lt;ChannelOption&lt;?&gt;, Object&gt;[] currentChildOptions;
    final Entry&lt;AttributeKey&lt;?&gt;, Object&gt;[] currentChildAttrs;
    //选项转化为Entry对象(6)
    synchronized (childOptions) {
        currentChildOptions = childOptions.entrySet().toArray(newOptionArray(childOptions.size()));
    }
    //属性转化为Entry对象(7)
    synchronized (childAttrs) {
        currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(childAttrs.size()));
    }
    //添加服务端handler(8)
    p.addLast(new ChannelInitializer&lt;Channel&gt;() {
        //初始化channel
        @Override
        public void initChannel(Channel ch) throws Exception {
            final ChannelPipeline pipeline = ch.pipeline();
            ChannelHandler handler = config.handler();
            if (handler != null) {
                pipeline.addLast(handler);
            }
            ch.eventLoop().execute(new Runnable() {
                @Override
                public void run() {
                    pipeline.addLast(new ServerBootstrapAcceptor(
                            currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                }
            });
        }
    });
}

我们继续看第8步添加服务端handler

因为这里的handlerChannelInitializer, 所以完成添加之后会调用ChannelInitializerhandlerAdded方法

跟到handlerAdded方法

ChannelInitializer.handlerAdded(ChannelHandlerContext ctx)

public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
    //默认情况下, 会返回true
    if (ctx.channel().isRegistered()) {
        initChannel(ctx);
    }
}

因为执行到这步服务端channel已经完成注册, 所以会执行到initChannel方法

ChannelInitializer.initChannel(ChannelHandlerContext ctx)

private boolean initChannel(ChannelHandlerContext ctx) throws Exception {
    //这段代码是否被执行过
    if (initMap.putIfAbsent(ctx, Boolean.TRUE) == null) {
        try {
            initChannel((C) ctx.channel());
        } catch (Throwable cause) {
            exceptionCaught(ctx, cause);
        } finally {
            //调用之后会删除当前节点
            remove(ctx);
        }
        return true;
    }
    return false;
}

我们关注initChannel这个方法, 这个方法是在ChannelInitializer的匿名内部来实现的, 这里我们注意, 在initChannel方法执行完毕之后会调用remove(ctx)删除当前节点

继续跟进initChannel方法

public void initChannel(Channel ch) throws Exception {
    final ChannelPipeline pipeline = ch.pipeline();
    ChannelHandler handler = config.handler();
    if (handler != null) {
        pipeline.addLast(handler);
    }
    ch.eventLoop().execute(new Runnable() {
        @Override
        public void run() {
            pipeline.addLast(new ServerBootstrapAcceptor(
                    currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
        }
    });
}

这里首先添加用户自定义的handler, 这里如果用户没有定义, 则添加不成功, 然后, 会调用addLastServerBootstrapAcceptor这个handler添加了进去, 同样这个handler也继承了ChannelInboundHandlerAdapter, 在这个handler中, 重写了channelRead方法, 所以, 这就是第一个问题的答案

紧接着我们看第二个问题:客户端handler是什么时候被添加的?

看ServerBootstrapAcceptor的channelRead方法

public void channelRead(ChannelHandlerContext ctx, Object msg) {
    final Channel child = (Channel) msg;
    //添加channelHadler, 这个channelHandler, 就是用户代码添加的ChannelInitializer
    child.pipeline().addLast(childHandler);
    //代码省略
    try {
        //work线程注册channel
        childGroup.register(child).addListener(new ChannelFutureListener() {
            //代码省略
        });
    } catch (Throwable t) {
        forceClose(child, t);
    }
}

这里真相可以大白了, 服务端再创建完客户端channel之后, 将新创建的NioSocketChannel作为参数触发channelRead事件(可以回顾NioMessageUnsafe.read方法, 代码这里就不贴了), 所以这里的参数msg就是NioSocketChannel

拿到channel时候再将客户端的handler添加进去, 我们回顾客户端handler的添加过程:

.childHandler(new ChannelInitializer&lt;SocketChannel&gt;() {
    @Override
    public void initChannel(SocketChannel ch) {
        ch.pipeline().addLast(new StringDecoder());
        ch.pipeline().addLast(new StringEncoder());
        ch.pipeline().addLast(new ServerHandler());
    }
});

和服务端channel的逻辑一样, 首先会添加ChannelInitializer这个handler但是没有注册所以没有执行添加handler的回调, 将任务保存到一个延迟回调的task

等客户端channel注册完毕, 会将执行添加handler的回调, 也就是handlerAdded方法, 在回调中执行initChannel方法将客户端handler添加进去, 然后删除ChannelInitializer这个handler

因为在服务端channel中这块逻辑已经进行了详细的剖析, 所以这边就不在赘述, 同学们可以自己跟进去走一遍流程

这里注意, 因为每创建一个NioSoeketChannel都会调用服务端ServerBootstrapAcceptorchannelRead方法, 所以这里会将每一个NioSocketChannelhandler进行添加

总结

本文章分析了事件传输的相关逻辑, 包括handler的添加, 删除, inboundoutbound以及异常事件的传输, 最后结合第一章和第三章, 剖析了服务端channel和客户端channel的添加过程,更多关于netty服务端请求联合pipeline的资料请关注我们其它相关文章!

(0)

相关推荐

  • Netty分布式pipeline管道传播事件的逻辑总结分析

    目录 问题分析 首先完成了handler的添加, 但是并没有马上执行回调 回到callHandlerCallbackLater方法中 章节总结 我们在第一章和第三章中, 遗留了很多有关事件传输的相关逻辑, 这里带大家一一回顾 问题分析 首先看两个问题: 1.在客户端接入的时候, NioMessageUnsafe的read方法中pipeline.fireChannelRead(readBuf.get(i))为什么会调用到ServerBootstrap的内部类ServerBootstrapAccep

  • Netty分布式pipeline管道异常传播事件源码解析

    目录 传播异常事件 简单的异常处理的场景 我们跟到invokeChannelRead这个方法 我还是通过两种写法来进行剖析 跟进invokeExceptionCaught方法 跟到invokeExceptionCaught方法中 讲完了inbound事件和outbound事件的传输流程, 这一小节剖析异常事件的传输流程 传播异常事件 简单的异常处理的场景 @Override public void channelRead(ChannelHandlerContext ctx, Object msg

  • Netty分布式pipeline管道传播outBound事件源码解析

    目录 outbound事件传输流程 这里我们同样给出两种写法 跟到其write方法中: 跟到findContextOutbound中 回到write方法: 继续跟invokeWrite0 我们跟到HeadContext的write方法中 了解了inbound事件的传播过程, 对于学习outbound事件传输的流程, 也不会太困难 outbound事件传输流程 在我们业务代码中, 有可能使用wirte方法往写数据: public void channelActive(ChannelHandlerC

  • Netty分布式pipeline管道Handler的删除逻辑操作

    目录 删除handler操作 我们跟到getContextPrDie这个方法中 首先要断言删除的节点不能是tail和head 回到remove(ctx)方法 上一小节我们学习了添加handler的逻辑操作, 这一小节我们学习删除handler的相关逻辑 删除handler操作 如果用户在业务逻辑中进行ctx.pipeline().remove(this)这样的写法, 或者ch.pipeline().remove(new SimpleHandler())这样的写法, 则就是对handler进行删除

  • netty中pipeline异常事件分析

    目录 异常处理的场景 AbstractChannelHandlerContext.invokeChannelRead AbstractChannelHandlerContext.notifyHandlerException(Throwable cause) AbstractChannelHandlerContext.invokeExceptionCaught(final Throwable cause) 两种写法 DefualtChannelPipeline.fireExceptionCaugh

  • Netty分布式pipeline传播inbound事件源码分析

    前一小结回顾:pipeline管道Handler删除 传播inbound事件 有关于inbound事件, 在概述中做过简单的介绍, 就是以自己为基准, 流向自己的事件, 比如最常见的channelRead事件, 就是对方发来数据流的所触发的事件, 己方要对这些数据进行处理, 这一小节, 以激活channelRead为例讲解有关inbound事件的处理流程 在业务代码中, 我们自己的handler往往会通过重写channelRead方法来处理对方发来的数据, 那么对方发来的数据是如何走到chann

  • netty pipeline中的inbound和outbound事件传播分析

    目录 传播inbound事件 两种写法 DefaultChannelPipeline.fireChannelRead(msg) AbstractChannelHandlerContext.invokeChannelRead(head, msg) AbstractChannelHandlerContext.invokeChannelRead(m) HeadContext的channelRead方法 AbstractChannelHandlerContext.fireChannelRead(msg)

  • netty服务端处理请求联合pipeline分析

    目录 两个问题 NioMessageUnsafe.read() ServerBootstrap.init(Channel channel) ChannelInitializer的继承关系 PendingHandlerAddedTask构造方法 PendingHandlerCallback构造方法 回到callHandlerCallbackLater方法 AbstractChannel.register0(ChannelPromise promise) pipeline.invokeHandler

  • Netty启动流程服务端channel初始化源码分析

    目录 服务端channel初始化 回顾上一小节initAndRegister()方法 init(Channel)方法 前文传送门 Netty分布式server启动流程 服务端channel初始化 回顾上一小节initAndRegister()方法 final ChannelFuture initAndRegister() { Channel channel = null; try { //创建channel channel = channelFactory.newChannel(); //初始化

  • netty服务端辅助类ServerBootstrap创建逻辑分析

    目录 ServerBootstrap创建 核心参数 初始化流程 首先执行绑定 注册自身到 EventLoop 绑定端口逻辑 ServerBootstrap创建 ServerBootstrap 为 netty 建立服务端的辅助类, 以 NIO为例,创建代码如下: public static void main(String[] args) throws Exception { ServerBootstrap bs = new ServerBootstrap(); bs.group(new NioE

  • jQuery通过Ajax向PHP服务端发送请求并返回JSON数据

    JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式.易于人阅读和编写,同时也易于机器解析和生成.JSON在前后台交互的过程中发挥着相当出色的作用. 服务端PHP读取MYSQL数据,并转换成JSON数据,传递给前端Javascript,并操作JSON数据.本文将通过实例演示了jQuery通过Ajax向PHP服务端发送请求并返回JSON数据.阅读本文的读者应该具备jQuery.Ajax.PHP相关知识,并能熟练运用. XHTML <ul id="use

  • java如何实现post请求webservice服务端

    目录 post请求webservice服务端 1.例如我此时有一个wsdl文件 2.点击row查看具体的发送参数 3.代码实现 3.1参数说明 用post请求调用webservice post请求webservice服务端 当生成webService的客户端不好实现时,通过java的post请求不失为一种好办法. 1.例如我此时有一个wsdl文件 http://xxx.xxx.xxx.xxx:8081/APIService.svc?wsdl 2.通过SoapUI 我们可以将swdl文件转换.从而

  • vue服务端渲染操作简单入门实例分析

    本文实例讲述了vue服务端渲染操作.分享给大家供大家参考,具体如下: 想到要学习vue-ssr的同学,自不必多说,一定是熟悉了vue,并且多多少少做过几个项目.然后学习vue服务端渲染无非解决首屏渲染的白屏问题以及SEO友好. 话不多说,笔者也是研究多日才搞明白这个服务端渲染到底是杂么回事!!! 一,首先实现下官网的基本案例 随便建一个目录,然后执行npm init初始化项目,生成工程文件package.json:创建server.js:然后按照vue-ssr官方链接:https://ssr.v

  • 详解React项目的服务端渲染改造(koa2+webpack3.11)

    因为对网页SEO的需要,要把之前的React项目改造为服务端渲染,经过一番调查和研究,查阅了大量互联网资料.成功踩坑. 选型思路:实现服务端渲染,想用React最新的版本,并且不对现有的写法做大的改动,如果一开始就打算服务端渲染,建议直接用NEXT框架来写 项目地址:https://github.com/wlx200510/react_koa_ssr 脚手架选型:webpack3.11.0 + react Router4 + Redux + koa2 + React16 + Node8.x 主要

  • 详解React 服务端渲染方案完美的解决方案

    最近在开发一个服务端渲染工具,通过一篇小文大致介绍下服务端渲染,和服务端渲染的方式方法.在此文后面有两中服务端渲染方式的构思,根据你对服务端渲染的利弊权衡,你会选择哪一种服务端渲染方式呢? 什么是服务器端渲染 使用 React 构建客户端应用程序,默认情况下,可以在浏览器中输出 React 组件,进行生成 DOM 和操作 DOM.React 也可以在服务端通过 Node.js 转换成 HTML,直接在浏览器端"呈现"处理好的 HTML 字符串,这个过程可以被认为 "同构&qu

  • 阿里云OSS实践文件直传基于服务端

    目录 前言 优缺点 流程 逻辑拆解 代码实现 OSS 配置 policy 内容 有效期 文件名 转化 policy 进一步分析 完整代码 前言 在日常开发中,客户端上传文件的一般流程是:客户端向服务端发送文件,再由服务端将文件转储到专门的存储服务器或云计算厂商的储存服务(例如阿里云 OSS)上,这样做的一个弊端是上传环节占用服务器的带宽,个位数的并发上传就能把带宽占满,从而导致用户体验下降.而如果直接将文件从客户端直传到第三方的存储服务上,就可以避免这个问题. ​ 本文以阿里云 OSS(Obje

  • 基于SpringMVC+Bootstrap+DataTables实现表格服务端分页、模糊查询

    前言 基于SpringMVC+Bootstrap+DataTables实现数据表格服务端分页.模糊查询(非DataTables Search),页面异步刷新. 说明:sp:message标签是使用了SpringMVC国际化 效果 DataTable表格 关键字查询 自定义关键字查询,非DataTable Search 代码 HTML代码 查询条件代码 <!-- 查询.添加.批量删除.导出.刷新 --> <div class="row-fluid"> <di

随机推荐