ROS Source Learning V. Connection Processing

Keywords: Java

2021SC@SDUSC

Catalog

1. Write before

2. Design Mode - Abstract Factory

3. ConnectionServer Inheritance Level

4. ConnectionServer and its Grandfather Key Sources

1. Write before

        In the previous blog, we learned where to send socket handles after ServerSocket listened for client socket connections. We set up a job in which Connection is responsible for receiving and parsing messages and Connection Server is responsible for executing process calls. Next we will learn the run method of Connection and Connection Server.

2. Design Mode - Abstract Factory

        The factory model is designed to cope with the need for multiple products in one product class. It hides the details of creating product objects and improves scalability. Its general business process is to get the creation of plant objects - > configure plant parameters - > invoke plant-related methods to produce objects. While Abstract factories are used to solve multiple product problems that require a product family. In the abstract factory model, there is a super factory that creates factories to produce factories with different configurations.

        

        There is a WorkerFactory with a production processing unit in Connection Server, and the generation of this object follows the abstract factory model. After recognizing this, we understand the necessity when we see the complex process of the multilevel inheritance hierarchy of this factory and the generation of worker objects: WorkerFactory does not serve RPC only, so decouple as much as possible.

3. ConnectionServer Inheritance Level

        The following is a brief description of each level of functionality. XmlRpcController is an abstract class that declares how to get a factory and how to get a factory configuration.   XmlRpcServer implements XmlRpcController, and implements methods to get registered method call handles, get output streams, call handles, and return response objects. XmlHttpServer adds methods to set response headers, and Connection Server overrides methods to set corresponding headers and return response objects.

4. ConnectionServer and its Grandfather Key Sources

         In this analysis of source code, to understand its inheritance relationship and the role it plays in system operation, start with the run method of Connection in the previous blog, because the execute method of XmlRpcServer called in the run method is the only bridge that connections intersect with Connection Server.

 public void run()
    {
        try
        {
            for (int i = 0; ; i++)
            {
                RequestData data = getRequestConfig();  
                if (data == null)
                {
                    break;
                }
                server.execute(data, this);  
                output.flush();
                if (!data.isKeepAlive() || !data.isSuccess()) 
                {
                    break;
                }
            }
        } catch (RequestException e)
        {
            webServer.log(e.getClass().getName() + ": " + e.getMessage());
            try
            {
                writeErrorHeader(e.requestData, e, -1);
                output.flush();
            } catch (IOException e1)
            {
                /* Ignore me */
            }
        } catch (Throwable t)
        {
            if (!shuttingDown)
            {
                webServer.log(t);
            }
        } finally
        {
            try
            {
                output.close();
            } catch (Throwable ignore)
            {
            }
            try
            {
                input.close();
            } catch (Throwable ignore)
            {
            }
            try
            {
                socket.close();
            } catch (Throwable ignore)
            {
            }
        }
    }

         In the run method, first enter the try code block, then make a perpetual for loop. In the loop body, first parse the client's HTTP message and get the RequestData object, which encapsulates the process that the client calls. Next, determine if the RequestData is empty, if it exits, otherwise call the XmlRpcServer's execute method. Pass in RequestData as a parameter. Next check if the client requires a TCP connection to continue and exit the loop if it does not require a continuous connection or if the execution of a procedure call fails. Otherwise, continue parsing and executing the procedure call. Enter the appropriate cache code block when an exception occurs, write to the log, and close the output stream. Finally enter the finally code block to close all stream objects.

        The run method calls the execute method of the server object, which is declared as an instance of XmlRpcServer, whose actual type is ConnectionServer. This is the clue that will be used to analyze the key code for ConnectionServer and grandparent classes.

        

public void execute(XmlRpcStreamRequestConfig pConfig,
						ServerStreamConnection pConnection)
			throws XmlRpcException {
		log.debug("execute: ->");
		try {
			Object result;
			Throwable error;
			InputStream istream = null;
			try {
				istream = getInputStream(pConfig, pConnection);
				XmlRpcRequest request = getRequest(pConfig, istream);
				if (request.getMethodName().equals("system.multicall")) {
					result = executeMulticall(request);
				} else {
					result = execute(request); 
				}
				istream.close();
				istream = null;
				error = null;
				log.debug("execute: Request performed successfully");
			} catch (Throwable t) {
				logError(t);
				result = null;
				error = t;
			} finally {
				if (istream != null) { try { istream.close(); } catch (Throwable ignore) {} }
			}
			boolean contentLengthRequired = isContentLengthRequired(pConfig);
			ByteArrayOutputStream baos;
			OutputStream ostream;
			if (contentLengthRequired) {
				baos = new ByteArrayOutputStream();
				ostream = baos;
			} else {
				baos = null;
				ostream = pConnection.newOutputStream();
			}
			ostream = getOutputStream(pConnection, pConfig, ostream);
			try {
				if (error == null) {
					writeResponse(pConfig, ostream, result);
				} else {
					writeError(pConfig, ostream, error);
				}
				ostream.close();
				ostream = null;
			} finally {
				if (ostream != null) { try { ostream.close(); } catch (Throwable ignore) {} }
			}
			if (baos != null) {
				OutputStream dest = getOutputStream(pConfig, pConnection, baos.size());
				try {
					baos.writeTo(dest);
					dest.close();
					dest = null;
				} finally {
					if (dest != null) { try { dest.close(); } catch (Throwable ignore) {} }
				}
			}
            pConnection.close();
			pConnection = null;
		} catch (IOException e) {
			throw new XmlRpcException("I/O error while processing request: "
					+ e.getMessage(), e);
		} finally {
			if (pConnection != null) { try { pConnection.close(); } catch (Throwable ignore) {} }
		}
		log.debug("execute: <-");
	}

        The execute method actually called inherits the method of XmlRpcStreamServer for Connection Server, which receives an XmlRpcStreamRequestConfig object of the actual type RequestData and a ServerStreamConnection object of the actual type Connection.

        Enter the try code block first, and then call the Connection method to get the input stream and the Request object, which encapsulates the body of the request, an XML file containing the methods and parameters that need to be invoked. After you get the Request object, determine if it has the name "System.multicall". If it is a multiple call, go to the executeMulticall method. Otherwise, call the method execute to get the result object result, write a log after closing the input stream object, send the result object back to the client, and finally call the close method of pConnection (which actually overrides the close method of the empty implementation for the Connection class).

	
	private Object[] executeMulticall(final XmlRpcRequest pRequest) {
		if (pRequest.getParameterCount() != 1)
			return null;

		Object[] reqs = (Object[]) pRequest.getParameter(0); 
		ArrayList<Object> results = new ArrayList<Object>(); 
		final XmlRpcRequestConfig pConfig = pRequest.getConfig();
		// TODO: make concurrent calls?
		for (int i = 0; i < reqs.length; i++) {
			Object result = null;
			try {
				@SuppressWarnings("unchecked")
				HashMap<String, Object> req = (HashMap<String, Object>) reqs[i];
				final String methodName = (String) req.get("methodName");
				final Object[] params = (Object[]) req.get("params");
				result = execute(new XmlRpcRequest() {
					@Override
					public XmlRpcRequestConfig getConfig() {
						return pConfig;
					}

					@Override
					public String getMethodName() {
						return methodName;
					}

					@Override
					public int getParameterCount() {
						return params == null ? 0 : params.length;
					}

					@Override
					public Object getParameter(int pIndex) {
						return params[pIndex];
					}
				});
			} catch (Throwable t) {
				logError(t);
				
				result = null;
			}
			results.add(result);
		}
		Object[] retobj = new Object[] { results };
		return retobj;
	}

        In the execute method, if its RPC is a multiple call, a list of returned objects is obtained through executeMulticall. In the executeMulticall method, a for loop is entered to iterate through each call request, encapsulated as an XmlRequest, and executed by the execute method, and the result is returned as an Object array.

//XmlRpcServer
public Object execute(XmlRpcRequest pRequest) throws XmlRpcException {
	    final XmlRpcWorkerFactory factory = getWorkerFactory();
	    final XmlRpcWorker worker = factory.getWorker();
        try {
            return worker.execute(pRequest);
        } finally {
            factory.releaseWorker(worker);
        }
	}


//XmlRpcWorkerFactory
public synchronized XmlRpcWorker getWorker() throws XmlRpcLoadException {
		int max = controller.getMaxThreads();
		if (max > 0  &&  numThreads == max) {
			throw new XmlRpcLoadException("Maximum number of concurrent requests exceeded: " + max);
		}
		if (max == 0) {
			return singleton;
		}
        ++numThreads;
		if (pool.size() == 0) {
			return newWorker();
		} else {
			return (XmlRpcWorker) pool.remove(pool.size() - 1);
		}
	}



//XmlRpcServerWorkerFactory
protected XmlRpcWorker newWorker() {
		return new XmlRpcServerWorker(this);
	}

        The execute method is a method inherited from XmlRpcServer and receives an XmlRpcRequest as a parameter. First, it calls the getWorkerFactory method of XmlRpcServer to get the XmlRpcWorkerFactory object, which is actually of type XmlRpcServerWorkerFactory. Then it calls the getWorker method of XmlRpcWorkerFactory to get a Worker object. getWorker accesses the XmlRpcController object. (Actual type is ConnectionServer) and check its maximum number of workers, if 0, return the singleton, otherwise check if there is a worker in the current worker pool and return it if there is one, otherwise call the newWorker method to return a.newWorker method that is overridden in the XmlRpcWorkerFactory class and return an XmlRpcServerWorker.

        Call its execute method after the worker object is obtained and return the result, releasing the worker object in its finally block of code;

//XmlRpcServerWorker
public Object execute(XmlRpcRequest pRequest) throws XmlRpcException {
		XmlRpcServer server = (XmlRpcServer) getController();
		XmlRpcHandlerMapping mapping = server.getHandlerMapping();
		XmlRpcHandler handler = mapping.getHandler(pRequest.getMethodName());
		return handler.execute(pRequest);
	}


//XmlRpcServer
public XmlRpcHandlerMapping getHandlerMapping() {
		return handlerMapping;
	}

        The execute method first calls the getController method to get the XmlRpcServer (the actual type is ConnectionServer), then calls the getHandler Mapping method of XmlRpcServer to get the method name and the mapping XmlRpcHandler Mapping of the method handle, then uses the mapping object to get the handle XmlRpcHandler object, then calls the execute method of the handle and returns the result.

        The registration and management of the execute method are implemented by the related classes in the node package, so it will not be repeated in this blog, but will be explained in detail in future analysis.In short, the four-tier execute calls of Connection concatenate the functions of Connection Server and its grandparents, together with the abstract factory mode of its production worker, which greatly reduces the coupling and is worth further discussion in the future. Use for reference in software development.         

Posted by neo0506 on Sun, 31 Oct 2021 10:15:38 -0700