All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.webpieces.http2engine.impl.shared.Level2ParsingAndRemoteSettings Maven / Gradle / Ivy

package com.webpieces.http2engine.impl.shared;

import java.util.List;
import org.webpieces.util.futures.XFuture;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.webpieces.data.api.DataWrapper;
import org.webpieces.util.acking.ByteAckTracker;

import com.webpieces.hpack.api.HpackParser;
import com.webpieces.hpack.api.UnmarshalState;
import com.webpieces.http2.api.dto.error.CancelReasonCode;
import com.webpieces.http2.api.dto.error.ConnectionException;
import com.webpieces.http2.api.dto.error.StreamException;
import com.webpieces.http2.api.dto.highlevel.Http2Trailers;
import com.webpieces.http2.api.dto.lowlevel.DataFrame;
import com.webpieces.http2.api.dto.lowlevel.GoAwayFrame;
import com.webpieces.http2.api.dto.lowlevel.PingFrame;
import com.webpieces.http2.api.dto.lowlevel.PriorityFrame;
import com.webpieces.http2.api.dto.lowlevel.RstStreamFrame;
import com.webpieces.http2.api.dto.lowlevel.SettingsFrame;
import com.webpieces.http2.api.dto.lowlevel.UnknownFrame;
import com.webpieces.http2.api.dto.lowlevel.WindowUpdateFrame;
import com.webpieces.http2.api.dto.lowlevel.lib.Http2Msg;
import com.webpieces.http2engine.api.client.Http2Config;
import com.webpieces.http2engine.api.error.ConnectionClosedException;
import com.webpieces.http2engine.api.error.ConnectionFailure;
import com.webpieces.http2engine.api.error.ReceivedGoAway;
import com.webpieces.http2engine.impl.shared.data.HeaderSettings;

public abstract class Level2ParsingAndRemoteSettings {

	private static final Logger log = LoggerFactory.getLogger(Level2ParsingAndRemoteSettings.class);

	private HpackParser lowLevelParser;
	private UnmarshalState parsingState;
	private Level7MarshalAndPing marshalLayer;
	private String logId;
	protected Level3IncomingSynchro syncro;
	private HeaderSettings localSettings;
	private ByteAckTracker tracker2 = new ByteAckTracker();

	private TempTimeoutSet streamsToDiscard = new TempTimeoutSet();
	
	protected Level3OutgoingSynchro outSyncro;

	public Level2ParsingAndRemoteSettings(
			String logId,
			Level3IncomingSynchro syncro,
			Level3OutgoingSynchro outSyncro,
			Level7MarshalAndPing notifyListener, 
			HpackParser lowLevelParser, 
			Http2Config config
	) {
		this.outSyncro = outSyncro;
		this.localSettings = config.getLocalSettings();
		this.logId = logId;
		
		this.syncro = syncro;
		this.marshalLayer = notifyListener;
		this.lowLevelParser = lowLevelParser;
		parsingState = lowLevelParser.prepareToUnmarshal(logId, 4096, localSettings.getHeaderTableSize(), localSettings.getMaxFrameSize());
	}

	/**
	 * NOT thread safe!!! BUT channelmanager keeps everything virtually thread-safe (ie. it's all going to come in order)
	 * @return 
	 * @return 
	 */
	public XFuture parse(DataWrapper newData) {
		XFuture future;
		try {
			future = parseImpl(newData);
		} catch(Throwable t) {
			future = new XFuture();
			future.completeExceptionally(t);
		}
		
		return future.handle((resp, t) -> handleFinalError(resp, t));
	}
	private Void handleFinalError(Object object, Throwable e) {
		if(e == null)
			return null;
		else if(e instanceof ConnectionException) {
			log.error("shutting the connection down due to error", e);
			ConnectionFailure reset = new ConnectionFailure((ConnectionException)e);
			syncro.sendGoAwayToSvrAndResetAllToApp(reset).exceptionally( t -> logExc("connection", t)); //send GoAway
		} else if(e instanceof StreamException) {
			log.error("Stream had an error", e);
			streamsToDiscard.add(((StreamException) e).getStreamId());
			syncro.sendRstToServerAndApp((StreamException) e).exceptionally( t -> logExc("stream", t));
		} else {
			log.error("shutting the connection down due to error(MAKE sure your clients try..catch, exceptions)", e);
			ConnectionException exc = new ConnectionException(CancelReasonCode.BUG, logId, 0, e.getMessage(), e);
			ConnectionFailure reset = new ConnectionFailure((ConnectionException)exc);
			syncro.sendGoAwayToSvrAndResetAllToApp(reset).exceptionally( t -> logExc("connection", t)); //send GoAwa
		}
		return null;
	}
	
	private Void handleError(Object object, Throwable e) {
		if(e == null) 
			return null;
		else if(e instanceof ConnectionClosedException) {
			log.error("Normal exception since we are closing and they do not know yet", e);
		} else if(e instanceof StreamException) {
			log.error("shutting the stream down due to error", e);
			syncro.sendRstToServerAndApp((StreamException) e).exceptionally( t -> logExc("stream", t));
		} else 
			handleFinalError(object, e);
		
		return null;
	}

	private Void logExc(String thing, Throwable t) {
		log.error("error trying to close "+thing, t);
		return null;
	}
	
	public XFuture parseImpl(DataWrapper newData) {
		
		parsingState = lowLevelParser.unmarshal(parsingState, newData);
		
		List parsedMessages = parsingState.getParsedFrames();
		
		//All the below futures must be chained with previous ones in case previous ones are not
		//done which will serialize it all to be in sequence
		XFuture future = parsingState.getProcessFuture();
		
		for(Http2Msg lowLevelFrame : parsedMessages) {
			//VERY IMPORTANT: Writing the code like this would slam through calling process N times
			//BUT it doesn't give the clients a chance to seet a flag between packets
			//Mainly done for exceptions and streaming so you can log exc, set a boolean so you
			//don't get 100 exceptions while something is happening like socket disconnect
			//In these 2 lines of code, processCorrectly is CALLED N times RIGHT NOW
			//The code below this only calls them right now IF AND ONLY IF the client returns
			//a completed future each time!!!
//			XFuture messageFuture = process(lowLevelFrame);
//			allFutures = allFutures.thenCompose( f -> messageFuture);
			
			future = future.thenCompose( f -> process(lowLevelFrame));
		}
		
		parsingState.setProcessFuturee(future);
		
		return future;
	}

	public XFuture process(Http2Msg msg) {
		if(streamsToDiscard.checkDiscard(msg.getStreamId()))
			return XFuture.completedFuture(null); //this is a stream that failed so discard frames for a bit
		
		XFuture future = new XFuture();
		try {
			future = processImpl(msg);
		} catch(Throwable e) {
			future.completeExceptionally(e);
		}
		
		return future.handle((v, t) -> handleError(v, t));
	}
	
	public XFuture processImpl(Http2Msg msg) {
		if(log.isDebugEnabled())
			if(log.isDebugEnabled())
				log.debug(logId+"frame from socket="+msg);
		
		if(msg instanceof DataFrame) {
			return syncro.sendDataToApp((DataFrame) msg);
		} else if(msg instanceof Http2Trailers) {
			return syncro.sendTrailersToApp((Http2Trailers)msg);
		} else if(msg instanceof PriorityFrame) {
			return syncro.sendPriorityFrameToApp((PriorityFrame) msg);
		} else if(msg instanceof RstStreamFrame) {
			return syncro.sendRstToApp((RstStreamFrame) msg);
		} else if(msg instanceof UnknownFrame) {
			return syncro.sendUnkownFrameToApp((UnknownFrame)msg);
		} else if(msg instanceof GoAwayFrame) {
			ReceivedGoAway goAway = new ReceivedGoAway(logId+" Far end sent goaway to us", (GoAwayFrame)msg);
			return syncro.sendGoAwayToApp(goAway).exceptionally( t -> logExc("connection", t)); //send GoAwa
		} else if(msg instanceof PingFrame) {
			return marshalLayer.processPing((PingFrame)msg);
		} else if(msg instanceof SettingsFrame) {
			return processHttp2SettingsFrame((SettingsFrame) msg);
		} else if(msg instanceof WindowUpdateFrame){
			return syncro.updateWindowSize((WindowUpdateFrame)msg);
		} 
		
		return processSpecific(msg);
	}

	protected abstract XFuture processSpecific(Http2Msg msg);

	private XFuture processHttp2SettingsFrame(SettingsFrame settings) {
		if(settings.isAck()) {
			log.info("server acked our settings frame");
			return XFuture.completedFuture(null);
		} else {
			log.info("applying remote settings frame");
			
			return syncro.applyRemoteSettingsAndAck(settings);
		}
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy