HttpRequestDispatcher.java

/*
 * Copyright (C) 2012-2024 RRiBbit.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.rribbit.dispatching;

import java.io.InputStream;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.rribbit.Request;
import org.rribbit.Response;
import org.rribbit.processing.HttpRequestProcessorServlet;
import org.rribbit.util.Base64Util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This {@link RequestDispatcher} dispatches a {@link Request} to an {@link HttpRequestProcessorServlet} via HTTP. Failover and loadbalancing are not supported in this class,
 * as opposed to the {@link RmiRequestDispatcher}. The reason for this is that failover and loadbalancing are typically done in another layer of the HTTP stack, which
 * does not apply to RMI.
 * <p />
 * This class uses Apache HTTPClient to make the connection to the {@link HttpRequestProcessorServlet}. It uses the default functionality of Apache HTTPClient to do its work.
 * If you want to use more advanced functionality of Apache HTTPClient to make the connection, then it should be fairly straightforward to extend this class or implement your
 * own {@link RequestDispatcher} implementation with this class as an example.
 *
 * @author G.J. Schouten
 *
 */
public class HttpRequestDispatcher implements RequestDispatcher {

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

	protected String url;

	/**
	 * Whenever you use this constructor, be sure to set the url with the setter provided by this class. If you don't, runtime exceptions will occur.
	 */
	public HttpRequestDispatcher() {}

	/**
	 * Creates an {@link HttpRequestDispatcher} that will send the {@link Request}s to the specified URL. An {@link HttpRequestProcessorServlet} is expected to be listening
	 * at this URL. The URL is a standard HTTP URL, such as "http://host:port/path". It can also be an HTTPS URL, beginning with 'https:'.
	 *
	 * This constructor is recommended, since it forces you to specify the url. Passing a null value for this will result in runtime exceptions whenever
	 * the {@link HttpRequestDispatcher} is used.
	 *
	 * @param url
	 */
	public HttpRequestDispatcher(String url) {
		this.url = url;
	}

	@Override
	public <T> Response<T> dispatchRequest(Request request) {

		log.info("Dispatching HTTP Request");
		HttpPost httpPost = null;
		try {
			log.info("Encoding Request");
			String requestString = Base64Util.encodeObject(request);

			log.info("Dispatching encoded Request");
			try(CloseableHttpClient httpclient = HttpClientBuilder.create().build()) {
				httpPost = new HttpPost(url);
				httpPost.setEntity(new StringEntity(requestString));
				HttpResponse httpResponse = httpclient.execute(httpPost);
				HttpEntity entity = httpResponse.getEntity();

				try(InputStream inputStream = entity.getContent()) {
					log.info("Decoding Response");
					Response<T> response = Base64Util.decodeInputStream(inputStream);

					log.info("Returning Response");
					return response;
				}
			}
		} catch(Exception e) {
			log.error("Something went wrong during the Request. Below are some common errors that can occur in the stacktrace: ");
			log.error("\tStreamCorruptedException: Indicates that the client does not understand the response from the server. This may mean that the client gets " +
				"a 404 page back, which it cannot decode. Please see the client's org.apache.http logs to see the response that the client got from the server. Please " +
				"also check the URL that you have given the client.");
			log.error("\tEOFException: Indicates that the client does not get a response from the server. Please check the server logs to see what's wrong.");

			throw new RuntimeException(e);
		} finally {
			if(httpPost != null) {
				httpPost.releaseConnection();
			}
		}
	}

	public String getUrl() {
		return url;
	}

	public void setUrl(String url) {
		this.url = url;
	}
}