It is illegal to call this method if the current request is not in asynchron

本文详细介绍了在使用Spring AOP进行接口请求日志记录时遇到的错误及其解决方案,特别是如何处理ServletRequest和ServletResponse对象导致的序列化问题。

切面报错写法:

/*
@auther aa

@create_date 2019-12-24 15:15

@Desc 接口请求日志切面

**/

import com.alibaba.fastjson.JSON;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

@Aspect
@Component
public class WebLogAcpect {
    protected static final Logger logger = LoggerFactory.getLogger(WebLogAcpect.class);

    /**
     * 定义切入点
     * */
    @Pointcut("execution(* com.smcv.xyx.partOrder.manage.controller.view..*.*(..))")
    public void webLog(){}

    /**
     * 前置通知
     * */
    @Before("webLog()")
    public void doBefore(JoinPoint joinPoint)throws Exception{
        //接收到请求,打印请求内容
        ServletRequestAttributes attributes =(ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        //记录请求内容
        logger.info("-------------log print start--------");
        logger.info("请求接口名:{},请求方式:{}",request.getRequestURI().toString(),request.getMethod());
//报错位置在此下方
//报错位置在此下方
//报错位置在此下方
        logger.info("请求参数:{}",JSON.toJSONString(joinPoint.getArgs()));

    }

    @AfterReturning(returning="ret",pointcut = "webLog()")
    public void doAfterReturning(Object ret)throws Exception{
        ServletRequestAttributes attributes =(ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        //接收到响应,打印请求内容
        logger.info("请求接口:{}的响应为:{}",request.getRequestURI(),JSON.toJSONString(ret));
        logger.info("-------------log print end--------");

    }

}

因为:当请求中带有ServletRequest或ServletResponse时无法进行序列号,因而报错

【解决方式】

/*
@auther aa

@create_date 2019-12-24 15:15

@Desc 接口请求日志切面

**/

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

@Aspect
@Component
public class WebLogAcpect {
    protected static final Logger logger = LoggerFactory.getLogger(WebLogAcpect.class);

    /**
     * 定义切入点
     * */
    @Pointcut("execution(* com.smcv.xyx.partOrder.manage.controller.view..*.*(..))")
    public void webLog(){}

    /**
     * 前置通知
     * */
    @Before("webLog()")
    public void doBefore(JoinPoint joinPoint)  {
        Object[] args = joinPoint.getArgs();
        Object[] arguments = new Object[args.length];
        for (int i = 0; i < args.length; i++) {
            if (args[i] instanceof ServletRequest || args[i] instanceof ServletResponse || args[i] instanceof MultipartFile) {
                //ServletRequest不能序列化,从入参里排除,否则报异常:java.lang.IllegalStateException: It is illegal to call this method if the current request is not in asynchronous mode (i.e. isAsyncStarted() returns false)
                //ServletResponse不能序列化 从入参里排除,否则报异常:java.lang.IllegalStateException: getOutputStream() has already been called for this response
                continue;
            }
            arguments[i] = args[i];
        }
        String paramter = "";
        if (arguments != null) {
            try {
                paramter = JSONObject.toJSONString(arguments);
            } catch (Exception e) {
                paramter = arguments.toString();
            }
        }
        logger.info("请求接口名:{}", joinPoint.getSignature().getName());
        logger.info("请求参数:{}", paramter);
    }

    @AfterReturning(returning="ret",pointcut = "webLog()")
    public void doAfterReturning(Object ret)throws Exception{
        ServletRequestAttributes attributes =(ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        //接收到响应,打印请求内容
        logger.info("请求接口:{}的响应为:{}",request.getRequestURI(),JSON.toJSONString(ret));
        logger.info("-------------log print end--------");

    }

}

 

@Slf4j @Component public class BidirectionRequestHeaderProcessor extends AuthRequiredHeaderProcessor<BidirectionRequestChannel, BidirectionRequestChannelInformation> { private ChannelManager channelManager; private ContainerManager containerManager; private CloudAccessChannelHelper cloudAccessChannelHelper; @Autowired public BidirectionRequestHeaderProcessor(AuthClient authClient, BidirectionRequestHeaderProcessorProp prop, ChannelManager channelManager, ContainerManager containerManager, CloudAccessChannelHelper cloudAccessChannelHelper) { super(authClient, prop); this.channelManager = channelManager; this.containerManager = containerManager; this.cloudAccessChannelHelper = cloudAccessChannelHelper; } @Override protected BidirectionRequestChannel doBeforeAddExecutor(BidirectionRequestChannel bidirectionChannel) { if (log.isDebugEnabled()) { log.debug("[sid:{}] Disable auto read of Bidirection channel:", bidirectionChannel.getSid()); } bidirectionChannel.disableAutoRead(); return bidirectionChannel; } @Override public void doHeaderProcess(BidirectionRequestChannel bidirectionChannel) throws Exception { // Add into channel manager String channelId = bidirectionChannel.getChannelId(); channelManager.addRequestChannel(channelId, bidirectionChannel); // Add life time event if necessary. As the bidirection has sink channel characteristic, the life time event // should be created before channel adding into container. if (bidirectionChannel instanceof ILifeTimeControl) { ((ILifeTimeControl) bidirectionChannel).createLifeTimeEvent(ILifeTimeControl.LifeTimeType.CON_IDLE); } // Obtain the container. BidirectionContainer container = containerManager.obtainBidirectionContainer(bidirectionChannel); if (container == null) { log.info("[sid:{}] Bidirection channel is kicked off by others when obtaining container.", bidirectionChannel.getSid()); bidirectionChannel.sendHttpResponseAndClose(503, "Kicked off", RelayConsts.CloseReason.BIDIRECTION_KICKED_OFF); return; } if (bidirectionChannel instanceof CloudAccessBidirectionRequestChannel) { doCloudAccessHeaderProcess(bidirectionChannel, container); } // Initialize the container. if (log.isDebugEnabled()) { log.debug("[sid:{}] Bidirection channel obtain container successfully: container={}", bidirectionChannel.getSid(), container.toString()); } boolean hasPeerChannel = bidirectionChannel.initContainer(container); if (hasPeerChannel) { if (log.isDebugEnabled()) { log.debug("[sid:{}] Initialize Bidirection channel after container initialization", bidirectionChannel.getSid()); } bidirectionChannel.startDataAcceptanceAfterSinkChannelAccess(); } } public void doCloudAccessHeaderProcess(final BidirectionRequestChannel bidirectionChannel, BidirectionContainer container) throws Exception { RequestChannel cloudAccessChannel = cloudAccessChannelHelper .createCloudAccessChannel(bidirectionChannel.getBindingId(), bidirectionChannel.getInformation().getOrignalRequest(), bidirectionChannel.getNettyChannel()); if (cloudAccessChannel instanceof CloudAccessBidirectionRequestChannel) { CloudAccessBidirectionRequestChannel cloudRelayChannel = (CloudAccessBidirectionRequestChannel) cloudAccessChannel; channelManager.addRequestChannel(cloudRelayChannel.getChannelId(), cloudRelayChannel); cloudRelayChannel.createLifeTimeEvent(ILifeTimeControl.LifeTimeType.CON_IDLE); containerManager.obtainBidirectionContainer(cloudRelayChannel); cloudRelayChannel.initContainer(container); } else { log.error("failed to create cloud access channel for {}", bidirectionChannel.getBindingId()); } } } @Slf4j @Component public class PassthroughGetRequestDataProcessor extends AbstractProcessor { private ChannelManager channelManager; @Autowired public PassthroughGetRequestDataProcessor(ChannelManager channelManager) { this.channelManager = channelManager; } @Override protected PassthroughGetRequestChannel doProcess(PassthroughGetRequestChannel passthroughGetChannel) throws Exception { String bindingId = passthroughGetChannel.getBindingId(); PostRequestChannel postChannel = (PostRequestChannel) channelManager.getRequestChannel(bindingId); if (postChannel == null) { log.info("[sid:{}] POST channel is missing when passthrough GET data arrives.", passthroughGetChannel.getSid()); return passthroughGetChannel; } // Start Get channel transmission: only for 1.3 multiple mapping request passthroughGetChannel.startTransmission(postChannel); List<IHttpData> dataList = passthroughGetChannel.getDataList(); IHttpResponseGenerator generator = postChannel.getSuccessResponseGenerator(); while (!dataList.isEmpty()) { IHttpData data = dataList.remove(0); if (generator != null && !data.isLastData()) { postChannel.sendData(generator.getHttpData(data)); } else { // Maybe POST channel is Version 1.2, this should not happen. data.release(); } } return passthroughGetChannel; } @Override public void exceptionCaught(PassthroughGetRequestChannel passthroughGetChannel, Throwable cause) { if (cause instanceof IllegalArgumentException || cause instanceof IllegalRequestException) { log.error("[sid:{}] Illegal request data:", passthroughGetChannel.getSid(), cause); passthroughGetChannel.close(RelayConsts.CloseReason.ILLEGAL_REQUEST_DATA_FORMAT); } else { log.error("[sid:{}] PassthroughGetRequestDataProcessor failed:", passthroughGetChannel.getSid(), cause); passthroughGetChannel.close(RelayConsts.CloseReason.SERVER_INTERNAL_ERROR); } } } @Slf4j @Component public class BufferedRequestDataProcessor extends RelayPostRequestDataProcessor { @Override protected DataProcessState processData(BufferedPostRequestChannel bufferedPostChannel, DataProcessState dataProcessState, IHttpData data) { if (dataProcessState == DataProcessState.TRANSMIT_DIRECT) { return super.transmitDataDirectly(bufferedPostChannel, DataProcessState.TRANSMIT_DIRECT, data); } else if (dataProcessState == DataProcessState.WANT_BUFFERED_DATA) { if (log.isDebugEnabled()) { log.debug("[sid:{}] Process buffered data: index={}", bufferedPostChannel.getSid(), data.getIndex()); } // store buffered data int left = bufferedPostChannel.addBufferedHttpData(data); if (left > 0) { // transmit buffered data to bound channels bufferedPostChannel.transmitData(data); // keep state return DataProcessState.WANT_BUFFERED_DATA; } else if (left == 0) { // transmit buffered data to bound channels bufferedPostChannel.transmitData(data); // change data process stat to DataProcessStat. return bufferedPostChannel.changeToNextState(); } else { // This will not happen super.transmitDataDirectly(bufferedPostChannel, DataProcessState.TRANSMIT_DIRECT, data); return bufferedPostChannel.changeToNextState(); } } else if (dataProcessState == DataProcessState.WANT_RESPONSE_MOULD) { return super.processHttpResponseMould(bufferedPostChannel, data); } else { // Use direct transmission as default action. This case should not happen. return super.transmitDataDirectly(bufferedPostChannel, DataProcessState.TRANSMIT_DIRECT, data); } } } @Slf4j @Component(“relayPostRequestDataProcessor”) public class RelayPostRequestDataProcessor extends PostRequestDataProcessor { @Override protected DataProcessState processData(T postChannel, DataProcessState dataProcessState, IHttpData data) { if (dataProcessState == DataProcessState.TRANSMIT_DIRECT) { return super.transmitDataDirectly(postChannel, DataProcessState.TRANSMIT_DIRECT, data); } else if (dataProcessState == DataProcessState.WANT_RESPONSE_MOULD) { return processHttpResponseMould(postChannel, data); } else { // Use direct transmission as default action. This case should not happen. return super.transmitDataDirectly(postChannel, DataProcessState.TRANSMIT_DIRECT, data); } } /** * Parse the response mould into response header, set it into POST request channel and transmit it to all GET * request channels related with the POST request channel. * * @param postChannel The POST request channel which received the response mould data. * @param data The response mould data */ protected DataProcessState processHttpResponseMould(T postChannel, IHttpData data) throws IllegalRequestException { log.debug("[sid:{}] Process first data: index={}", postChannel.getSid(), data.getIndex()); try { String content = null; if (data instanceof MultipartHttpData) { content = ((MultipartHttpData) data).addedContents().toString(data.getContentCharset()); } else { content = data.toString(); } HttpResponse httpResponse = parseHttpResponse(content); // If the Transfer-Encoding: chunked is set, do not modified as Chunked is used to measure the message // length of the HTTP response even if the Content-Length is set at the same time. if (!HttpUtil.isTransferEncodingChunked(httpResponse)) { String type = postChannel.getInformation().getParams().get(RelayConsts.ParamKey.TYPE); if (RelayConsts.Type.FILE.equals(type)) { // If the type is file, the Content-Length field in response should be the file size and multipart // should not be used. if (postChannel.getInformation().getContentType().isMultiPart() && httpResponse.headers().contains(HttpHeaderNames.CONTENT_LENGTH)) { throw new IllegalRequestException( "file type with multipart package should use Transfer-Encoding: chunked."); } } else { // If the type is other value, only when the Content-Length field in response does not exist or is // negtive, new Content-Length will be set. if (HttpUtil.getContentLength(httpResponse, -1L) < 0) { long contentLength = HttpUtil.getContentLength(postChannel.getInformation().getOrignalRequest(), -1L); httpResponse.headers().set(RelayConsts.HttpNames.CONTENT_LENGTH, contentLength > 0 ? contentLength : Long.MAX_VALUE); } } } // if status is 429, it shows device has too many relay connections at the same time // if (postChannel.hasSegmenter() && // httpResponse.status().code() == HttpResponseStatus.TOO_MANY_REQUESTS.code()) { // postChannel.stopSegmenterWhenCanNotGetVideoStream(RelayConsts.SegmenterStopReason.RELAY_CONNECTION_EXCEEDED); // } postChannel.setResponseHeader(httpResponse); postChannel.transmitHttpResponse(httpResponse); return postChannel.changeToNextState(); } finally { data.release(); } } /** * Parse response header from {@link String} format to {@link HttpResponse} format. * * @param stringValue {@link String} format of response header. * * @return {@link HttpResponse} format of response header. */ public HttpResponse parseHttpResponse(String stringValue) throws IllegalRequestException { String[] parts = stringValue.split("\r\n", 2); if (parts.length != 2 || parts[1].isEmpty()) { throw new IllegalRequestException("Invalid first data as a response header: " + stringValue); } String[] statusLine = parts[0].split(" ", 3); if (statusLine.length != 3 || statusLine[1].isEmpty() || statusLine[2].isEmpty()) { throw new IllegalRequestException("Invalid first data with an invalid response line: " + parts[0]); } HttpResponse httpResponse = null; try { HttpResponseStatus status = new SpecificCodeHttpResponseStatus(statusLine[1], statusLine[2]); httpResponse = new DefaultHttpResponse(HttpVersion.HTTP_1_1, status); } catch (NumberFormatException e) { throw new IllegalRequestException("Invalid first data with an unresovled status code: " + parts[0]); } String[] entries = parts[1].split("\r\n"); for (String entry : entries) { String[] content = entry.split(":", 2); if (content.length != 2 || content[1].isEmpty()) { throw new IllegalRequestException("Invalid first data with an unresovled header: " + entry); } String name = content[0].trim(); String value = content[1].trim(); if (name.isEmpty()) { throw new IllegalRequestException("Invalid first data with an empty key of header:" + entry); } httpResponse.headers().add(name, value); } // Netty channel of GET could not be reused. httpResponse.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE); httpResponse.headers().set(HttpHeaderNames.PRAGMA, HttpHeaderValues.NO_CACHE); httpResponse.headers().set(HttpHeaderNames.CACHE_CONTROL, HttpHeaderValues.NO_CACHE); return httpResponse; } } @Slf4j @Component(“postRequestDataProcessor”) public class PostRequestDataProcessor extends AbstractProcessor { @Override protected T doProcess(T sourceChannel) throws IllegalRequestException { DataProcessState dataProcessStat = sourceChannel.getDataProcessState(); List<IHttpData> dataList = sourceChannel.getDataList(); while (!dataList.isEmpty()) { IHttpData data = dataList.remove(0); dataProcessStat = processData(sourceChannel, dataProcessStat, data); } return sourceChannel; } protected DataProcessState processData(T sourceChannel, DataProcessState dataProcessState, IHttpData data) { return transmitDataDirectly(sourceChannel, dataProcessState, data); } protected DataProcessState transmitDataDirectly(T sourceChannel, DataProcessState dataProcessStat, IHttpData data) { assert dataProcessStat == DataProcessState.TRANSMIT_DIRECT; // Transmit the data sourceChannel.transmitData(data); // Release data original reference data.release(); return DataProcessState.TRANSMIT_DIRECT; } @Override public void exceptionCaught(T sourceChannel, Throwable cause) { if (cause instanceof IllegalArgumentException || cause instanceof IllegalRequestException) { log.error("[sid:{}] Illegal request data format:", sourceChannel.getSid(), cause); sourceChannel.close(RelayConsts.CloseReason.ILLEGAL_REQUEST_DATA_FORMAT); } else { log.error("[sid:{}] PostRequestDataProcessor failed:", sourceChannel.getSid(), cause); sourceChannel.close(RelayConsts.CloseReason.SERVER_INTERNAL_ERROR); } } } @Slf4j public class DefaultGetRequestChannel extends GetRequestChannel implements ILifeTimeControl, IHeartBeatControl { private static final RuntimeException HEART_BEAT_TIMEOUT_EXCEPTION = new RuntimeException("Heart beat timeout"); protected ILifeTimeControl lifeTimeController; protected AtomicLong closedTimeStamp; public DefaultGetRequestChannel(String channelId, Channel nettyChannel, String bindingId, int basicLifeTime, GetRequestChannelInformation information) { super(channelId, nettyChannel, bindingId, information); this.lifeTimeController = buildLifeTimeController(nettyChannel, basicLifeTime); this.closedTimeStamp = new AtomicLong(System.currentTimeMillis() + IHeartBeatControl.PROLONGED_TIME_MS); } protected ILifeTimeControl buildLifeTimeController(Channel nettyChannel, int basicLifeTime) { IEventCallBack callback = new IEventCallBack() { @Override public void handleEvent() { if (log.isDebugEnabled()) { log.debug("[sid:{}] Channel is closing as timeout: channelType={}", getSid(), DefaultGetRequestChannel.this.getClass().getSimpleName()); } sendHttpResponseAndClose(HttpResponseStatus.NOT_FOUND, RelayConsts.CloseReason.GET_LIFETIME_EXPIRE); } }; return new LifeTimeController(getSid(), ILifeTimeControl.GET_IDLE_TIME_S, basicLifeTime, EventTimer.getLifeTimeEventKey(nettyChannel), getClass().getSimpleName(), callback); } @Override protected ISinkHandler buildBasicSinkHandler(GetRequestChannelInformation information) { assert !information.isFFmpegChannel(); if (isAudio(information)) { return new AudioSinkHandler(this); } else { return new DefaultSinkHandler<>(this); } } private boolean isAudio(GetRequestChannelInformation information) { String type = information.getParams().get(RelayConsts.ParamKey.TYPE); switch (type) { case RelayConsts.Type.AUDIO: return true; case RelayConsts.Type.NVR: case RelayConsts.Type.SMART_NVR: String resolution = information.getParams().get(RelayConsts.ParamKey.RESOLUTION); if (resolution == null) { return false; } switch (resolution) { case RelayConsts.AudioResolution.AAC: case RelayConsts.AudioResolution.MP2: case RelayConsts.AudioResolution.PCM: return true; case RelayConsts.VideoResolution.HD: case RelayConsts.VideoResolution.QVGA: case RelayConsts.VideoResolution.VGA: default: return false; } default: return false; } } @Override public long getClosedTimeStamp() { return closedTimeStamp.get(); } @Override public void setClosedTimeStamp(long timeStamp) { closedTimeStamp.lazySet(timeStamp); information.addHeartBeatNum(); } @Override public String getLifeTimeEventKey() { return lifeTimeController.getLifeTimeEventKey(); } @Override public int getBasicLifeTime() { return lifeTimeController.getBasicLifeTime(); } @Override public int createLifeTimeEvent(LifeTimeType type) throws TimerEventException { return lifeTimeController.createLifeTimeEvent(type); } @Override public void prolongLeftTimeTo(int newLifeTime) throws TimerEventException { lifeTimeController.prolongLeftTimeTo(newLifeTime); } @Override public void shortenLeftTimeTo(int newLifeTime) { lifeTimeController.shortenLeftTimeTo(newLifeTime); } @Override public void removeLifeTimeEvent() { lifeTimeController.removeLifeTimeEvent(); } /** * {@inheritDoc} */ @Override public boolean updateAfterContainerInitialization(ISourceRequestChannel postRequestChannel) { try { boolean isSuccess = super.updateAfterContainerInitialization(postRequestChannel); if (isSuccess) { lifeTimeController.prolongLeftTimeTo(lifeTimeController.getBasicLifeTime()); } return isSuccess; } catch (Exception e) { log.error("[sid:{}] Exception caught when updating Get channel after container initialization", getSid(), e); return false; } } /** * {@inheritDoc} */ @Override public boolean updateWhenBinding(PostRequestChannel postRequestChannel) { try { boolean isSuccess = super.updateWhenBinding(postRequestChannel); if (isSuccess) { createLifeTimeEvent(LifeTimeType.BASIC); } return isSuccess; } catch (Exception e) { log.error("[sid:{}] Exception caught when binding Get channel", getSid(), e); return false; } } /** * If the heart beat is expired, the Get channel will be closed instead of sending template data. At this time, the * template data reference will not be increased. * * @param templateData The template data */ @Override public ChannelFuture sendTemplateData(IHttpData templateData) { if (System.currentTimeMillis() > getClosedTimeStamp()) { if (log.isDebugEnabled()) { log.debug("[sid:{}] Close GET channel as losing heartbeat:", getSid()); } close(RelayConsts.CloseReason.GET_LACK_HEART_BEAT); DefaultChannelPromise promise = new DefaultChannelPromise(getNettyChannel()); promise.setFailure(HEART_BEAT_TIMEOUT_EXCEPTION); return promise; } else { return super.sendTemplateData(templateData); } } @Override protected void releaseResourceAfterNettyChannelClosure() { super.releaseResourceAfterNettyChannelClosure(); removeLifeTimeEvent(); } } public class PassthroughGetRequestChannel extends DefaultGetRequestChannel implements IDataFollowedRelayRequestChannel { private RelayServiceDataController<PassthroughGetRequestChannel> dataController; private volatile boolean isStartDataProcess; public PassthroughGetRequestChannel(String channelId, Channel nettyChannel, String bindingId, // ClientInfoController clientInfoController, int basicLifeTime, GetRequestChannelInformation information) { super(channelId, nettyChannel, bindingId, // clientInfoController, basicLifeTime, information); this.dataController = new RelayServiceDataController<>(this, false); } @Override public boolean isStartDataProcess() { return isStartDataProcess; } @Override public void setStartDataProcess() { this.isStartDataProcess = true; } @Override public boolean updateAfterContainerInitialization(ISourceRequestChannel postRequestChannel) { if (super.updateAfterContainerInitialization(postRequestChannel)) { setStartDataProcess(); enableAutoRead(); addPushDataTask(); return true; } else { return false; } } /** * {@inheritDoc} */ @Override public boolean updateWhenBinding(PostRequestChannel postRequestChannel) { if (super.updateWhenBinding(postRequestChannel)) { setStartDataProcess(); enableAutoRead(); addPushDataTask(); return true; } else { return false; } } @Override public void disableAutoRead() { dataController.disableAutoRead(); } @Override public void enableAutoRead() { dataController.enableAutoRead(); } @Override public List<IHttpData> getDataList() { return dataController.getDataList(); } @Override public void addPushDataTask() { dataController.addPushDataTask(); } @Override protected void releaseResourceAfterNettyChannelClosure() { super.releaseResourceAfterNettyChannelClosure(); dataController.releaseDataList(); } } @Slf4j public class BidirectionRequestChannel extends SourceRequestChannel<BidirectionContainer, BidirectionRequestChannelInformation> implements ISinkRequestChannel { private DefaultSinkHandler<BidirectionRequestChannel> sinkHandler; public BidirectionRequestChannel(String channelId, Channel nettyChannel, String bindingId, BidirectionRequestChannelInformation information) { super(channelId, nettyChannel, bindingId, information); this.sinkHandler = new DefaultSinkHandler<>(this); } @Override public String getRequestType() { if (isDeviceChannel()) { return "relayservice-deviceStream"; } else { return "relayservice-appStream"; } } @Override public boolean initContainer(BidirectionContainer container) { assert this.container == null : "initContainer() should only be called once."; this.container = container; BidirectionRequestChannel peerChannel = getPeerDirectionChannel(container); if (peerChannel == null) { return false; } if (peerChannel.updateAfterContainerInitialization(this)) { return true; } else { // As peer channel is closing, this bidirection channel will be closing soon. close(RelayConsts.CloseReason.BIDIRECTION_CLOSED_BY_PEER); return false; } } public BidirectionRequestChannel getPeerDirectionChannel() { BidirectionContainer container = this.container; if (container != null) { return getPeerDirectionChannel(container); } else { return null; } } private BidirectionRequestChannel getPeerDirectionChannel(BidirectionContainer container) { if (isDeviceChannel()) { return container.getAppChannel(); } else { return container.getDeviceChannel(); } } /** * Only contains peer channel if it exists. */ @Override protected List<? extends ISinkRequestChannel> getTransmittedSinkRequestChannels() { BidirectionContainer container = this.container; if (container == null) { return new ArrayList<>(); } List<BidirectionRequestChannel> list = new ArrayList<>(); BidirectionRequestChannel peerChannel = getPeerDirectionChannel(container); if (peerChannel != null) { list.add(peerChannel); } return list; } /** * Do nothing, waiting terminal to close the request channel. */ @Override protected void doProcessBeforeTransmitData(IHttpData templateData) { // do nothing } @Override protected void transmitDataToSinkRequestChannel(ISinkRequestChannel sinkChannel, IHttpData templateData) { // 当channel来自回放请求时,channel为长连接,此时为了适配web无法发起正常http长连接,手动将lastData的空包丢掉 String type = this.getInformation().getParamValue(RelayConsts.ParamKey.TYPE); if ((Objects.equals(type, RelayConsts.Type.SDVOD) || Objects.equals(type, RelayConsts.Type.DOWNLOAD)) && templateData.isLastData()) { return; } sinkChannel.sendTemplateData(templateData); } @Override public void startDataAcceptanceAfterSinkChannelAccess() { assert container != null : "initContainer should be called first"; BidirectionRequestChannel peerChannel = getPeerDirectionChannel(container); if (peerChannel == null) { log.warn("[sid:{}] Peer channel is missing, may be closed?", getSid()); return; } if (information.getStatisticInformation().getIsWs()) { // Start data process setStartDataProcess(); // Enable auto read enableAutoRead(); return; } doBindInitialization(peerChannel); } @Override public DataProcessState getDataProcessState() { return DataProcessState.TRANSMIT_DIRECT; } @Override public DataProcessState changeToNextState() { return DataProcessState.TRANSMIT_DIRECT; } @Override public boolean updateAfterContainerInitialization(ISourceRequestChannel sourceRequestChannel) { if (log.isDebugEnabled()) { log.debug("[sid:{}] Do bind initialization after container initialization", getSid()); } doBindInitialization(sourceRequestChannel); return true; } protected void doBindInitialization(ISourceRequestChannel sourceRequestChannel) { // Update sid String sourceSid = sourceRequestChannel.getSid(); if (sourceSid.indexOf('-') == -1) { String oldSid = getSid(); if (oldSid.indexOf('-') == -1) { updateSid(sourceSid + '-' + oldSid); } } // Try to send response header HttpResponse responseHeader = sourceRequestChannel.getResponseHeader(); if (responseHeader != null) { sendTemplateHttpResponse(responseHeader); } // Start data process setStartDataProcess(); // Enable auto read enableAutoRead(); // Add push data task addPushDataTask(); } @Override public ChannelFuture sendTemplateHttpResponse(HttpResponse templateHttpResponse) { return sinkHandler.sendTemplateHttpResponse(templateHttpResponse); } @Override public ChannelFuture sendTemplateHttpResponseAndClose(HttpResponse templateHttpResponse, RelayConsts.CloseReason closeReason) { return sinkHandler.sendTemplateHttpResponseAndClose(templateHttpResponse, closeReason); } @Override public ChannelFuture sendTemplateData(IHttpData templateData) { return sinkHandler.sendTemplateData(templateData); } @Override public ChannelFuture sendTemplateDataAndClose(IHttpData templateData, RelayConsts.CloseReason closeReason) { return sinkHandler.sendTemplateDataAndClose(templateData, closeReason); } @Override protected void releaseResourceBeforeNettyChannelClosure() { if (information.getCloseReason() != RelayConsts.CloseReason.BIDIRECTION_CLOSED_BY_PEER) { ContainerManager.getInstance().removeBidirectionContainer(this); } super.releaseResourceBeforeNettyChannelClosure(); } } @Slf4j public abstract class SourceRequestChannel<T extends IRequestChannelContainer<? extends ISinkRequestChannel>, I extends AbstractTransactionRequestChannelInformation> extends TransactionRequestChannel implements ISourceRequestChannel { private String bindingId; private ChannelTrafficShapingHandler trafficHandler; private RelayServiceDataController<SourceRequestChannel<T, I>> dataController; private volatile boolean isStartDataProcess; private HttpResponse responseHeader; protected T container; public SourceRequestChannel(String channelId, Channel nettyChannel, String bindingId, I information) { super(channelId, nettyChannel, information); this.bindingId = bindingId; this.dataController = new RelayServiceDataController<>(this, false); } @Override public String getBindingId() { return bindingId; } public ChannelTrafficShapingHandler getTrafficHandler() { return trafficHandler; } public void setTrafficHandler(ChannelTrafficShapingHandler trafficHandler) { this.trafficHandler = trafficHandler; } @Override public void disableAutoRead() { dataController.disableAutoRead(); } @Override public void enableAutoRead() { dataController.enableAutoRead(); } @Override public List<IHttpData> getDataList() { return dataController.getDataList(); } @Override public void addPushDataTask() { dataController.addPushDataTask(); } @Override public HttpResponse getResponseHeader() { return responseHeader; } @Override public void setResponseHeader(HttpResponse responseHeader) { this.responseHeader = responseHeader; } @Override public boolean isStartDataProcess() { return isStartDataProcess; } @Override public void setStartDataProcess() { this.isStartDataProcess = true; } /** * Initialize the source channel with a container in which the sink channel is put into. * * @param container The container * * @return True if the container has at least one sink channel. */ public abstract boolean initContainer(T container); public boolean isInitContainer() { return container != null; } public T getContainer() { return container; } /** * Only used for Junit test. */ void setContainer(T container) { this.container = container; } /** * Start source channel data acceptance after sink channel access. */ public abstract void startDataAcceptanceAfterSinkChannelAccess(); /** * {@inheritDoc} */ @Override public void transmitHttpResponse(HttpResponse templateHttpResponse) { List<? extends ISinkRequestChannel> currentList = getTransmittedSinkRequestChannels(); for (ISinkRequestChannel sinkChannel : currentList) { try { sinkChannel.sendTemplateHttpResponse(templateHttpResponse); } catch (Exception e) { log.error("[sid:{}] Failed to transmit response to GET channel: channelId={}:", getSid(), sinkChannel.getChannelId(), e); sinkChannel.close(RelayConsts.CloseReason.SERVER_INTERNAL_ERROR); } } } /** * {@inheritDoc} */ @Override public void transmitData(IHttpData templateData) { List<? extends ISinkRequestChannel> currentList = templateData.isLastData() ? getTransmittedSinkRequestChannels() : null; doProcessBeforeTransmitData(templateData); currentList = currentList == null ? getTransmittedSinkRequestChannels() : currentList; for (ISinkRequestChannel sinkChannel : currentList) { try { transmitDataToSinkRequestChannel(sinkChannel, templateData); } catch (Exception e) { log.error("[sid:{}] Failed to transmit data to GET channel: channelId={}, index={}", getSid(), sinkChannel.getChannelId(), templateData.getIndex(), e); sinkChannel.close(RelayConsts.CloseReason.SERVER_INTERNAL_ERROR); } } } /** * Obtain all sink channel which can be transmitted response and data to. * * @return The sink channel which can be transmitted response and data to. */ protected abstract List<? extends ISinkRequestChannel> getTransmittedSinkRequestChannels(); /** * Process before transmit template data. * * @param templateData The template data */ protected abstract void doProcessBeforeTransmitData(IHttpData templateData); /** * Transmit template data to sink channel. * * @param sinkChannel The sink channel * @param templateData The template data. */ protected abstract void transmitDataToSinkRequestChannel(ISinkRequestChannel sinkChannel, IHttpData templateData); @Override protected void releaseResourceAfterNettyChannelClosure() { super.releaseResourceAfterNettyChannelClosure(); dataController.releaseDataList(); } } @Slf4j public abstract class TransactionRequestChannel extends RequestChannel { protected String channelId; @Getter @Setter private String content; public TransactionRequestChannel(String channelId, Channel nettyChannel, I information) { super(nettyChannel, information); this.channelId = channelId; } public String getChannelId() { return channelId; } public ChannelVersion getChannelVersion() { return information.getChannelVersion(); } public boolean isDeviceChannel() { return information.isFromDevice(); } public AbstractAuthContext getAuthContext() { return information.getAuthContext(); } public RequestLevel getRequestLevel() { return getAuthContext().getLevel(); } @Override protected void releaseResourceBeforeNettyChannelClosure() { // Call super method super.releaseResourceBeforeNettyChannelClosure(); // Reomve itself from ChannelManager immediately if (isRequestChannelRemovedImmediately()) { removeFromChannelManager(); } } @Override protected void releaseResourceAfterNettyChannelClosure() { // Call super method super.releaseResourceAfterNettyChannelClosure(); // Reomve itself from ChannelManager after 1s. if (!isRequestChannelRemovedImmediately()) { getNettyChannel().eventLoop().schedule(new Runnable() { @Override public void run() { removeFromChannelManager(); } }, 1000, TimeUnit.MILLISECONDS); } } protected boolean isRequestChannelRemovedImmediately() { return true; } ; protected void removeFromChannelManager() { if (log.isDebugEnabled()) { log.debug("[sid:{}] Remove request channel {} channelId={} from channel manager", getSid(), this.getClass().getSimpleName(), getChannelId()); } ChannelManager.getInstance().removeRequestChannel(channelId, this); } } @Slf4j public class GetRequestChannel extends TransactionRequestChannel implements ISinkRequestChannel { protected String bindingId; protected AtomicBoolean isAllowTransmission; protected ISinkHandler sinkHandler; public GetRequestChannel(String channelId, Channel nettyChannel, String bindingId, GetRequestChannelInformation information) { super(channelId, nettyChannel, information); this.bindingId = bindingId; this.isAllowTransmission = new AtomicBoolean(false); this.sinkHandler = buildSinkHandler(information); } /** * Build the sink hanlder according to the sink channel. When any template response and data are sent to this Get * channel, the sending action will be processed by using sink hanlder. * * @param information The Get channel information * * @return The sink hanlder. */ protected ISinkHandler buildSinkHandler(GetRequestChannelInformation information) { if (information.isNeedMultipleMappingSource()) { // Flow control is only used in Preview type(such as video or nvr) return new FlowControlHandler(buildBasicSinkHandler(information)); } else { return buildBasicSinkHandler(information); } } /** * The basic sink handler which is used to do actual sending action. * * @param information The Get channel information * * @return The basic sink hanlder. */ protected ISinkHandler buildBasicSinkHandler(GetRequestChannelInformation information) { return new DefaultSinkHandler<>(this); } @Override public String getRequestType() { if (getChannelVersion() == ChannelVersion.VERSION_1_2) { return "relayservice-getStream"; } else if (isDeviceChannel()) { return "relayservice-deviceStream"; } else { return "relayservice-appStream"; } } @Override public String getBindingId() { return bindingId; } public void updateBindingId(String newBindingId) { this.bindingId = newBindingId; } public boolean isAllowTransmission() { return isAllowTransmission.get(); } public boolean setAllowTransmission() { return this.isAllowTransmission.compareAndSet(false, true); } /** * Whether the type is a multiple mapping type, such as video, audio, mixed and nvr. * * @return True if the type is a multiple mapping type. */ public boolean isNeedMultipleMappingSource() { return information.isNeedMultipleMappingSource(); } /** * If the Get channel arrives before the corresponding Post channel, the Get channel will be put into a container. * When the Post channel arrives, the Post channel will find and initialize the container to check whether any Get * channel is bound with it. At this time, using this method the notify the Get channel of binding action after * container initialization. * <p> * <p> * This method will be called in following cases: * <ul> * <li>After the Post channel initializes the container, any Get channel in the container will be notified of the * initialization by calling this.</li> * <li>If the container has been initialized by Post channel when the Get channel is added into container directly. * The Get channel should be notified of the initialization by calling this.</li> * </ul> * </p> * * @param postRequestChannel The source channel which this sink channel is bound with. * * @return Whether the sink channel is successfully updated. If false, the sink channel may be closed and the source * channel should remove it. */ @Override public boolean updateAfterContainerInitialization(final ISourceRequestChannel postRequestChannel) { doBindInitialization(postRequestChannel); return true; } /** * Update Get channel when the Get channel is bound with Post channel. * * @param postRequestChannel The corresponding Post channel. * * @return True if the updating operation is successful. */ public boolean updateWhenBinding(final PostRequestChannel postRequestChannel) { doBindInitialization(postRequestChannel); return true; } private void doBindInitialization(final ISourceRequestChannel requestChannel) { EventLoop eventLoop = getNettyChannel().eventLoop(); if (eventLoop.inEventLoop()) { doBindInitializationInEventLoop(requestChannel); } else { eventLoop.execute(() -> doBindInitializationInEventLoop(requestChannel)); } } /** * The initialization of Get channel when bound with Post channel * * @param postRequestChannel The corresponding Post channel. */ protected void doBindInitializationInEventLoop(ISourceRequestChannel postRequestChannel) { // Update sid updateSidByPrefix(postRequestChannel.getSid()); // Try to send response header HttpResponse responseHeader = postRequestChannel.getResponseHeader(); if (responseHeader != null) { sendTemplateHttpResponse(responseHeader); } } /** * To initialize the Get request channel transmission. * * @param postRequestChannel The corresponding Post request channel. */ public void startTransmission(final PostRequestChannel postRequestChannel) { EventLoop eventLoop = getNettyChannel().eventLoop(); if (eventLoop.inEventLoop()) { doStartTransmission(postRequestChannel); } else { eventLoop.execute(() -> doStartTransmission(postRequestChannel)); } } private void doStartTransmission(PostRequestChannel postRequestChannel) { assert getNettyChannel().eventLoop().inEventLoop(); // This is helpful when the 2nd App for smart codec stream. initSmartCodec(postRequestChannel); if (setAllowTransmission()) { int num = postRequestChannel.updateTransmittedGetRequestChannels(); if (log.isDebugEnabled()) { log.debug("[sid:{}] Start transmission of GET channel: transmittedGetChannelNum={}", getSid(), num); } // This is helpful when the 1st App and smart codec notification may be comming soon. initSmartCodec(postRequestChannel); /* List<IHttpData> templateDataList = getDataListToStartTransmission(postRequestChannel); for (IHttpData templateData : templateDataList) { sendTemplateData(templateData); }*/ } } private void initSmartCodec(PostRequestChannel postRequestChannel) { if (postRequestChannel.isSmartCodec()) { startSmartCodec(); } else { stopSmartCodec(); } } /** * If the corresponding channel is {@link BufferedPostRequestChannel}, the buffered data will be returned. * Otherwise, empty array will be returned. * * @param postRequestChannel The corresponding Post request channel. * * @return The data list for initialization of transmission */ protected List<IHttpData> getDataListToStartTransmission(PostRequestChannel postRequestChannel) { List<IHttpData> dataList; if (postRequestChannel instanceof BufferedPostRequestChannel) { dataList = ((BufferedPostRequestChannel) postRequestChannel).getAllBufferedData(); if (log.isDebugEnabled()) { log.debug("[sid:{}] Add buffered HTTP data: size={}", getSid(), dataList.size()); } } else { dataList = new ArrayList<>(); } IHttpData iFrame = postRequestChannel.getIFrame(); if (iFrame != null) { dataList.add(iFrame); } return dataList; } @Override public ChannelFuture sendTemplateHttpResponse(HttpResponse templateHttpResponse) { return sinkHandler.sendTemplateHttpResponse(templateHttpResponse); } @Override public ChannelFuture sendTemplateHttpResponseAndClose(HttpResponse templateHttpResponse, RelayConsts.CloseReason closeReason) { return sinkHandler.sendTemplateHttpResponseAndClose(templateHttpResponse, closeReason); } @Override public ChannelFuture sendTemplateData(IHttpData templateData) { return sinkHandler.sendTemplateData(templateData); } @Override public ChannelFuture sendTemplateDataAndClose(IHttpData templateData, RelayConsts.CloseReason closeReason) { return sinkHandler.sendTemplateDataAndClose(templateData, closeReason); } public void startSmartCodec() { ISinkHandler handler = sinkHandler; FlowControlHandler flowControlHandler = findFlowControlHandler(handler); if (flowControlHandler != null) { flowControlHandler.setSmartCodec(true); } if (!(handler instanceof SmartCodecSinkHandler)) { sinkHandler = new SmartCodecSinkHandler(handler); } } public void stopSmartCodec() { ISinkHandler handler = sinkHandler; FlowControlHandler flowControlHandler = findFlowControlHandler(handler); if (flowControlHandler != null) { flowControlHandler.setSmartCodec(false); } if (handler instanceof SmartCodecSinkHandler) { sinkHandler = ((SmartCodecSinkHandler) handler).unwrap(); } } public void updateKeyFrameUTCTime(long utcTime) { FlowControlHandler h = findFlowControlHandler(sinkHandler); if (h != null) { h.updateUTCTime(utcTime); } } public void updateWritabilityEvent(boolean isWritable) { FlowControlHandler h = findFlowControlHandler(sinkHandler); if (h != null) { h.setSinkChannelWriterable(isWritable); } } private FlowControlHandler findFlowControlHandler(ISinkHandler h) { while (h instanceof FilterSinkHandler) { if (h instanceof FlowControlHandler) { return (FlowControlHandler) h; } else { h = ((FilterSinkHandler) h).unwrap(); } } return null; } @Override protected void doClose() { if (RelayConsts.Type.FILE.equals(information.getParams().get(RelayConsts.ParamKey.TYPE))) { // When the last data is sent, netty channel may be closed immediately and the elb will not discard the last // data. The delay added here is like SO_LINGER configuration. if (log.isDebugEnabled()) { log.debug("[sid={}] Delay close netty channel when file type", getSid()); } getNettyChannel().eventLoop().schedule(GetRequestChannel.super::doClose, 100, TimeUnit.MILLISECONDS); } else { super.doClose(); } } @Override protected void releaseResourceBeforeNettyChannelClosure() { // Call super method super.releaseResourceBeforeNettyChannelClosure(); // As this channel has unbounded, unbindGetRequestChannel() should be avoided. RelayConsts.CloseReason closeReason = information.getCloseReason(); if (closeReason == RelayConsts.CloseReason.GET_CLOSED_BY_POST || closeReason == RelayConsts.CloseReason.GET_CLOSED_AFTER_LAST_DATA_SENT || closeReason == RelayConsts.CloseReason.GET_CLOSED_BY_KILL_SHARER_CMD) { return; } ChannelManager manager = ChannelManager.getInstance(); PostRequestChannel postRequestChannel = (PostRequestChannel) manager.getRequestChannel(bindingId); if (postRequestChannel != null) { postRequestChannel.unbindGetRequestChannel(this); } else { ContainerManager.getInstance().removeFromSingleDirectionContainer(this); } } } @Slf4j public class BufferedPostRequestChannel extends DefaultPostRequestChannel { private volatile IHttpData[] buffedDataList; public BufferedPostRequestChannel(String channelId, Channel nettyChannel, String shortBindingId, DataProcessState initDataProcessState, int basicLifeTime, int bufferedNum, PostRequestChannelInformation information) { super(channelId, nettyChannel, shortBindingId, initDataProcessState, basicLifeTime, information); this.buffedDataList = new IHttpData[bufferedNum]; } /** * Try to add data into buffer. The return value represents the left number in the buffer after adding this data. If * the data is added successfully, the return value is non-negtive, especially 0 indicates the buffer is full after * adding this data. If the data is failed to be added into buffer when the buffer is already full, -1 will be * return. No matter the data is added successfully or not, the data reference will not be modified. * * @return left number in the buffer after adding this data. * <ul> * <li>If the value is positive, the buffer still have capcacity after adding this data</li> * <li>If the value is 0, the buffer is full after adding this data</li> * <li>If the value is -1, the data is not added into this data as the buffer is already full</li> * </ul> */ public int addBufferedHttpData(IHttpData bufferedHttpData) { IHttpData[] oldList = this.buffedDataList; int bufferedNum = oldList.length; int index = 0; for (; index < bufferedNum; index++) { if (oldList[index] == null) { break; } } if (index < bufferedNum) { IHttpData[] newList = Arrays.copyOf(oldList, bufferedNum); newList[index] = bufferedHttpData; this.buffedDataList = newList; return bufferedNum - (index + 1); } else { return -1; } } /** * Obtain all buffered data at this time. The return list will be instantiated each time this method is called. * * @return The buffered data list. */ public List<IHttpData> getAllBufferedData() { IHttpData[] current = this.buffedDataList; ArrayList<IHttpData> ret = new ArrayList<>(current.length); for (int index = 0; index < current.length; index++) { if (current[index] != null) { ret.add(current[index]); } else { break; } } return ret; } @Override protected DataProcessState getNextState(DataProcessState oldState) { if (oldState == DataProcessState.WANT_RESPONSE_MOULD) { return DataProcessState.WANT_BUFFERED_DATA; } else if (oldState == DataProcessState.WANT_BUFFERED_DATA) { return DataProcessState.TRANSMIT_DIRECT; } else if (oldState == DataProcessState.TRANSMIT_DIRECT) { return DataProcessState.TRANSMIT_DIRECT; } else { throw new RuntimeException("Bugs in data process state change:" + oldState); } } @Override protected void releaseResourceAfterNettyChannelClosure() { // release wave header if (log.isDebugEnabled()) { log.debug("[sid:{}] Release buffered HTTP data", getSid()); } IHttpData[] current = this.buffedDataList; this.buffedDataList = new IHttpData[0]; for (IHttpData data : current) { ReferenceCountUtil.safeRelease(data); } if (information.isFromDevice()) { log.debug("cloud access channel for {} from device is closing", getBindingId()); CloudStreamInfoContainer.getInstance().removeInfo(getBindingId()); } super.releaseResourceAfterNettyChannelClosure(); } } @Slf4j public class PostRequestChannel extends SourceRequestChannel<SingleDirectionContainer, PostRequestChannelInformation> { /* It is used for multiple mapping type when social share starts and the preview of single stream output IPC */ private String shortBindingId; private DataProcessState dataProcessState; private boolean isNeedResponseAfterGetAccess; private IHttpResponseGenerator successResponseGenerator; private IFrameHolder iFrameHolder; public PostRequestChannel(String channelId, Channel nettyChannel, String shortBindingId, DataProcessState initDataProcessState, PostRequestChannelInformation information) { super(channelId, nettyChannel, channelId, information); this.shortBindingId = shortBindingId; this.dataProcessState = initDataProcessState; if (this.getChannelVersion() == ChannelVersion.VERSION_1_2) { Boolean isNeedSuccessResponse = information.isNeedSuccessResponse(); if (isNeedSuccessResponse == null) { this.isNeedResponseAfterGetAccess = false; this.successResponseGenerator = buildSuccessResponseGenerator(nettyChannel.alloc()); } else { this.isNeedResponseAfterGetAccess = isNeedSuccessResponse; this.successResponseGenerator = isNeedSuccessResponse ? buildSuccessResponseGenerator(nettyChannel.alloc()) : null; } } else { this.isNeedResponseAfterGetAccess = true; this.successResponseGenerator = buildSuccessResponseGenerator(nettyChannel.alloc()); } } private IHttpResponseGenerator buildSuccessResponseGenerator(ByteBufAllocator alloc) { String boundary = ContentType.generateBoundary(); ContentType contentType = new ContentType("multipart/mixed", boundary, ContentType.DEFAULT_DATA_CHARSET); return new MultiPartResponseGenerator(alloc, contentType, HttpUtil.isTransferEncodingChunked(information .getOrignalRequest())); } @Override public String getRequestType() { if (getChannelVersion() == ChannelVersion.VERSION_1_2) { return "relayservice-postStream"; } else if (isDeviceChannel()) { return "relayservice-deviceStream"; } else { return "relayservice-appStream"; } } public boolean isMultipleMapping() { return information.isMultipleMapping(); } public boolean isNeedResponseAfterGetAccess() { return isNeedResponseAfterGetAccess; } public boolean isSingleStream() { return information.isSingleStream(); } public IHttpResponseGenerator getSuccessResponseGenerator() { return successResponseGenerator; } public String getShortBindingId() { return shortBindingId; } @Override public DataProcessState getDataProcessState() { return dataProcessState; } /** * Change to next state and return the new state. As this method is called in data processor only, the state field * only uses <code>volitale</code> instead of <code>AtomicReference</code> * * @return The next data process state. */ @Override public DataProcessState changeToNextState() { assert getNettyChannel().eventLoop().inEventLoop(); DataProcessState state = getNextState(this.dataProcessState); this.dataProcessState = state; return state; } protected DataProcessState getNextState(DataProcessState oldState) { if (oldState == DataProcessState.WANT_RESPONSE_MOULD) { return DataProcessState.TRANSMIT_DIRECT; } else if (oldState == DataProcessState.TRANSMIT_DIRECT) { return DataProcessState.TRANSMIT_DIRECT; } else { throw new RuntimeException("Bugs in data process state change:" + oldState); } } /** * Initialize the Post channel with a container. The Post channel only transmit data to the Get channel which has * the same version(except ffmpeg Get channel). If the Get channel version does not match with the Post version, the * Get channel will be response 404/400 and closed. Especially, when the Get channel is 1.2 version and Post channel * is 1.3 version, Get channel will be shown hint video whe next access. * * @param container The allocated container * * @return Whether the container has contained any Get channels, if <code>true</code>, the container contains GET * channels. */ @Override public boolean initContainer(SingleDirectionContainer container) { assert this.container == null : "initContainer() should only be called once."; synchronized (PostRequestChannel.class) { this.container = container; return doInitContainer(container); } } protected boolean doInitContainer(SingleDirectionContainer container) { boolean hasGetChannels = false; Iterator<GetRequestChannel> iter = container.boundIterator(); while (iter.hasNext()) { GetRequestChannel getChannel = iter.next(); if (getChannel.getChannelVersion() != getChannelVersion() // && !getChannel.isFFmpegChannel() ) { iter.remove(); information.addVersionUnmatchNum(); getChannel.getInformation().setVersionUnmatch(true); if (getChannel.getChannelVersion() == ChannelVersion.VERSION_1_2) { getChannel.sendHttpResponseAndClose(HttpResponseStatus.NOT_FOUND, CloseReason.GET_CLOSED_WITH_LOW_VERSION); } else { getChannel.sendHttpResponseAndClose(HttpResponseStatus.BAD_REQUEST, CloseReason.GET_CLOSED_WITH_HIGHT_VERSION); } continue; } if (getChannel.getChannelVersion() == ChannelVersion.VERSION_1_2) { getChannel.setAllowTransmission(); } boolean isSuccess = getChannel.updateAfterContainerInitialization(this); if (isSuccess) { isSuccess = doUpdateBindingInformation(getChannel); } if (isSuccess) { hasGetChannels = true; } else { iter.remove(); getChannel.sendHttpResponseAndClose(HttpResponseStatus.NOT_FOUND, CloseReason.GET_CLOSED_WHEN_BIND_FAILURE); } } if (hasGetChannels) { // As Post channel data doesnot arrive or is buffered in decoder, startTransmission of Get channel does // not need to be called. container.synchronizeTransmittedSinkRequestChannelList(); } return hasGetChannels; } /** * Update Get and Post channel after container initialization. This is called when Get channel is added into * container which has been initialized by this Post channel. * <ul> * <li>For Get channel, using {@link GetRequestChannel#updateAfterContainerInitialization(ISourceRequestChannel)} to * update Get channel.</li> * <li>For Post channel, updating binding information</li> * </ul> * * @param getRequestChannel The Get channel which is added into an initialized container. * * @return True if the updating operation is successfully executed. */ public boolean updateAfterContainerInitialization(GetRequestChannel getRequestChannel) { // As this method is called after isInitContainer() checking, the container has been initialized. assert container != null : "initContainer() or isInitContainer() should be called first"; SingleDirectionContainer container = this.container; synchronized (container) { if (getRequestChannel.getChannelVersion() == ChannelVersion.VERSION_1_2) { getRequestChannel.startTransmission(this); } boolean isSuccess = getRequestChannel.updateAfterContainerInitialization(this); if (isSuccess) { isSuccess = doUpdateBindingInformation(getRequestChannel); } if (!isSuccess) { container.removeBoundSinkRequestChannel(getRequestChannel); container.synchronizeTransmittedSinkRequestChannelList(); } return isSuccess; } } /** * Call it after Post channel is added into channel manager. This method is used to obtain and process data which is * stored in decoder, which is useful to sticky package for version 1.3. */ public void callAfterAddIntoChannelManager() { for (GetRequestChannel getChannel : getBoundGetRequestChannels()) { if (getChannel instanceof IDataFollowedRelayRequestChannel) { ((IDataFollowedRelayRequestChannel) getChannel).setStartDataProcess(); ((IDataFollowedRelayRequestChannel) getChannel).enableAutoRead(); ((IDataFollowedRelayRequestChannel) getChannel).addPushDataTask(); } } } /** * Bind the given Get channel with this Post channel and return the binding result. If the Post channel is closed, * false will be returned.The Post channel only transmit data to the Get channel which has the same version(except * ffmpeg Get channel). If the Get channel version does not match with the Post version, the binding result will be * false. * * @param getRequestChannel The Get channel needs to be bound with this Post channel * * @return The binding action is sucessful or not. True if the Get channel is successfully bound with this Post * channel. False if the Post channel is closed and fails to bind the Get channel. */ public BindingResult bindGetRequestChannel(GetRequestChannel getRequestChannel) { // As this method is called after PostRequestChannel being put into channel manager. the container has been // initialized. assert container != null : "initContainer() or isInitContainer() should be called first"; if (isClosed()) { return BindingResult.SOURCE_CHANNEL_CLOSURE; } SingleDirectionContainer container = this.container; synchronized (container) { return doBindGetRequestChannel(container, getRequestChannel); } } protected BindingResult doBindGetRequestChannel(SingleDirectionContainer container, GetRequestChannel getRequestChannel) { BindingResult result = container.addBoundSinkRequestChannel(getRequestChannel); if (result != BindingResult.SUCCESS) { return result; } if (getRequestChannel.getChannelVersion() == ChannelVersion.VERSION_1_2) { getRequestChannel.startTransmission(this); } if (BooleanUtils.isTrue(getRequestChannel.getWs())) { if (getRequestChannel instanceof IDataFollowedRelayRequestChannel) { ((IDataFollowedRelayRequestChannel) getRequestChannel).setStartDataProcess(); ((IDataFollowedRelayRequestChannel) getRequestChannel).enableAutoRead(); } return BindingResult.SUCCESS; } boolean isSuccess = getRequestChannel.updateWhenBinding(this); if (isSuccess) { isSuccess = doUpdateBindingInformation(getRequestChannel); } if (!isSuccess) { container.removeBoundSinkRequestChannel(getRequestChannel); container.synchronizeTransmittedSinkRequestChannelList(); } return isSuccess ? BindingResult.SUCCESS : BindingResult.UPDATE_INFORMATION_FAILURE; } private boolean doUpdateBindingInformation(GetRequestChannel getRequestChannel) { switch (getRequestChannel.getRequestLevel()) { case OWNER: information.addOwnerNum(); break; case SLAVE: information.addSlaveNum(); break; case SHARED: information.addSharedNum(); break; case SOCIAL: information.addSocialNum(); break; default: break; } return true; } /** * Send pull-stream command when ffmpeg channel arrives and the Post channel version is 1.3 */ public void sendPullStreamCommandIfNecessary() { if (getChannelVersion() == ChannelVersion.VERSION_1_3) { assert successResponseGenerator != null; HttpResponse response = successResponseGenerator.getHttpResponse(HttpResponseStatus.OK); if (response != null) { sendHttpResponse(response); } JSONObject command = buildPullStreamCommand(getInformation().getParams()); sendData(successResponseGenerator.getHttpData(command)); } } /** * <p> * Build pull-stream command. * </p> * <p> * In 1.3 version, channel of video type is 0, and channel of nvr type starts from 0 and has following mapping. * * <pre> * 'channel' in relay request url ----> 'channel' in JSON command * 0 ----> 0 * 1 ----> 1 * ... ----> ... * </pre> * * </p> * * @param params request url parameters. * * @return The pull-stream command */ private JSONObject buildPullStreamCommand(Map<String, String> params) { // 实际上设备使用此信息控制推流RESOLUTION int channel = 0; if (Type.NVR.equals(params.get(ParamKey.TYPE)) || Type.SMART_NVR.equals(params.get(ParamKey.TYPE))) { channel = Integer.parseInt(params.get(ParamKey.CHANNEL)); } JSONObject preview = new JSONObject(); preview.put("channels", new JSONArray().put(channel)); String resolution = params.get(ParamKey.RESOLUTION); if (resolution != null) { preview.put("resolutions", new JSONArray().put(resolution)); } JSONObject command = new JSONObject(); command.put("type", "request"); command.put("seq", "0"); command.put("params", new JSONObject().put("method", "get").put("preview", preview)); return command; } /** * {@inheritDoc} * * <ul> * <li>For 1.3 version Post channel, as the terminal will not send data before receiving pull-data command, the * response and auto read COULD be sent and enabled after Get access.</li> * <li>For 1.2 version Post channel, the response and auto read SHOULD be sent and enabled after Get access.</li> * </ul> */ @Override public void startDataAcceptanceAfterSinkChannelAccess() { // Send success response header if necessary if (isNeedResponseAfterGetAccess()) { HttpResponse httpResponse = successResponseGenerator.getHttpResponse(HttpResponseStatus.OK); if (httpResponse != null) { sendHttpResponse(httpResponse); } } // Start data process setStartDataProcess(); // Try to enable auto read of POST channel enableAutoRead(); // Add pull data task addPushDataTask(); } /** * Unbind the given GET channel. If the GET channel exists in the binding list, true will be returned. * * @param getRequestChannel The GET channel needs to be unbound. * * @return True if the GET channel exists in the binding list. */ public boolean unbindGetRequestChannel(GetRequestChannel getRequestChannel) { // As this method is called after PostRequestChannel being put into channel manager. the container has been // initialized. assert container != null : "Should call initContainer() first."; SingleDirectionContainer container = this.container; synchronized (container) { boolean isExisted = container.removeBoundSinkRequestChannel(getRequestChannel); if (isExisted) { // If the ffmpeg channel is closed explicitly, the channel will not exist in list. container.synchronizeTransmittedSinkRequestChannelList(); // 取消FFmpeg重试 // if (getRequestChannel.isFFmpegChannel()) { // doRestartSegmenter(container); // } else { doTryToClose(container, CloseReason.POST_CLOSED_AFTER_GET_EXIT); // } } return isExisted; } } /** * Try to close the Post channel. The closure strategy is shown in {@link #doTryToClose(SingleDirectionContainer, * CloseReason)} * * @param closeReason The specific close reason. */ public void tryToClose(CloseReason closeReason) { SingleDirectionContainer container = this.container; if (container == null) { return; } synchronized (container) { doTryToClose(container, closeReason); } } /** * Try to close current POST channel after GET channel is unbound. For single mapping channel, the POST channel will * be closed immediately if no GET channel is bound any more. For multiple mapping channel, the POST channel will be * closed in following cases: * <ul> * <li>When the bound channel number is 0, the segmenter doesn't exist or is in Segmenter.MAX_RESTART_RETRIES state, * or doesn't have consumer.</li> * <li>When the bound channel number is 1, the segmenter is in Segmenter.SEGMENTING state and doesn't have consumer</li> * </ul> * * @param closeReason The reason of closing action */ protected void doTryToClose(SingleDirectionContainer container, CloseReason closeReason) { // Before doTryToClose, container has been checked and could not be null. if (isMultipleMapping()) { // int bindedChannelNum = container.getBoundSinkRequestChannelNum(); // if (bindedChannelNum == 0) { // Segmenter s = segmenter; // if (s == null || s.getState() == Segmenter.MAX_RESTART_RETRIES || !s.hasConsumer()) { // close(closeReason); // } // } else if (bindedChannelNum == 1) { // Segmenter s = segmenter; // if (s != null && s.isSegmenting() && !s.hasConsumer()) { // close(closeReason); // } // } //log.error("Is multiple mapping"); close(closeReason); } else { if (container.hasNoBoundSinkRequestChannel()) { close(closeReason); } } } /** * Close all GET channels bound with this POST channel. The method is only used in {@link * #releaseResourceAfterNettyChannelClosure()} */ private void closeAllGetRequestChannels() { SingleDirectionContainer container = this.container; if (container == null) { return; } synchronized (container) { Iterator<GetRequestChannel> iter = container.boundIterator(); while (iter.hasNext()) { GetRequestChannel getChannel = iter.next(); if (log.isDebugEnabled()) { log.debug("[sid:{}] Close GET Channel: channelId={}", getSid(), getChannel.getChannelId()); } getChannel.close(CloseReason.GET_CLOSED_BY_POST); } container.removeAllBoundSinkeRequestChannels(); container.synchronizeTransmittedSinkRequestChannelList(); } } public List<GetRequestChannel> getBoundGetRequestChannels() { SingleDirectionContainer container = this.container; if (container == null) { return new ArrayList<>(); } synchronized (container) { return container.getBoundSinkRequestChannels(); } } public int getBoundGetRequestChannelNum() { SingleDirectionContainer container = this.container; if (container == null) { return 0; } synchronized (container) { return container.getBoundSinkRequestChannelNum(); } } @SuppressWarnings("unchecked") public List<GetRequestChannel> getTransmittedGetRequestChannels() { return (List<GetRequestChannel>) getTransmittedSinkRequestChannels(); } public int getTransmittedGetRequestChannelNum() { SingleDirectionContainer container = this.container; if (container == null) { return 0; } return container.getTransmittedSinkRequestChannelNum(); } public int updateTransmittedGetRequestChannels() { SingleDirectionContainer container = this.container; if (container == null) { return 0; } synchronized (container) { return container.synchronizeTransmittedSinkRequestChannelList(); } } public class ControlRequestChannel extends BaseHttpRequestChannel { public ControlRequestChannel(String channelId, Channel nettyChannel, ControlRequestChannelInformation information) { super(channelId, nettyChannel, ILifeTimeControl.IDLE_LIFE_TIME_S, information); } @Override public String getRequestType() { String cmdMethod = information.getCmdMethod(); return cmdMethod != null ? RelayConsts.Service.CONTROL + '-' + cmdMethod : RelayConsts.Service.CONTROL; } }
最新发布
09-11
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值