web-dev-qa-db-fra.com

Transférer HttpServletRequest à un autre serveur

J'ai reçu une requête HttpServletRequest dans ma Spring Servlet que je voudrais transmettre TELLE QUELLE (c'est-à-dire GET ou POST) à un autre serveur.

Quelle serait la meilleure façon de le faire en utilisant Spring Framework?

Dois-je récupérer toutes les informations et créer un nouveau HTTPUrlConnection? Ou existe-t-il un moyen plus simple?

34
user1144031

Malheureusement, il n'y a pas de moyen facile de le faire. Fondamentalement, vous devrez reconstruire la demande, notamment:

  • méthode HTTP correcte
  • paramètres de requête
  • demande des en-têtes (HTTPUrlConnection ne permet pas de définir un agent utilisateur arbitraire, "Java/1.* "est toujours ajouté, vous aurez besoin de HttpClient)
  • corps

C'est beaucoup de travail, sans oublier qu'il ne sera pas mis à l'échelle car chaque appel proxy de ce type occupera un thread sur votre machine.

Mon conseil: utilisez des sockets bruts ou netty et interceptez le protocole HTTP au niveau le plus bas, en remplaçant simplement certaines valeurs (comme l'en-tête Host) à la volée. Pouvez-vous fournir plus de contexte, pourquoi en avez-vous donc besoin?

12
Tomasz Nurkiewicz

Des discussions pour savoir si vous devriez faire suivre cette voie de côté, voici comment je l'ai fait:

package com.example.servlets;

import Java.net.HttpURLConnection;
import Java.net.URL;
import Java.util.Enumeration;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.example.servlets.GlobalConstants;

@SuppressWarnings("serial")
public class ForwardServlet extends HttpServlet {

    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse resp) {
        forwardRequest("GET", req, resp);
    }

    @Override
    public void doPost(HttpServletRequest req, HttpServletResponse resp) {
        forwardRequest("POST", req, resp);
    }

    private void forwardRequest(String method, HttpServletRequest req, HttpServletResponse resp) {
        final boolean hasoutbody = (method.equals("POST"));

        try {
            final URL url = new URL(GlobalConstants.CLIENT_BACKEND_HTTPS  // no trailing slash
                    + req.getRequestURI()
                    + (req.getQueryString() != null ? "?" + req.getQueryString() : ""));
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod(method);

            final Enumeration<String> headers = req.getHeaderNames();
            while (headers.hasMoreElements()) {
                final String header = headers.nextElement();
                final Enumeration<String> values = req.getHeaders(header);
                while (values.hasMoreElements()) {
                    final String value = values.nextElement();
                    conn.addRequestProperty(header, value);
                }
            }

          //conn.setFollowRedirects(false);  // throws AccessDenied exception
            conn.setUseCaches(false);
            conn.setDoInput(true);
            conn.setDoOutput(hasoutbody);
            conn.connect();

            final byte[] buffer = new byte[16384];
            while (hasoutbody) {
                final int read = req.getInputStream().read(buffer);
                if (read <= 0) break;
                conn.getOutputStream().write(buffer, 0, read);
            }

            resp.setStatus(conn.getResponseCode());
            for (int i = 0; ; ++i) {
                final String header = conn.getHeaderFieldKey(i);
                if (header == null) break;
                final String value = conn.getHeaderField(i);
                resp.setHeader(header, value);
            }

            while (true) {
                final int read = conn.getInputStream().read(buffer);
                if (read <= 0) break;
                resp.getOutputStream().write(buffer, 0, read);
            }
        } catch (Exception e) {
            e.printStackTrace();
            // pass
        }
    }
}

Évidemment, cela pourrait nécessiter un peu de travail en ce qui concerne la gestion des erreurs et autres, mais c'était fonctionnel. J'ai toutefois cessé de l'utiliser, car dans mon cas, il était plus facile de téléphoner directement au CLIENT_BACKEND que de gérer les cookies, l'authentification, etc. sur deux domaines distincts.

21
Brian White

J'avais également besoin de faire la même chose, et après des contrôles non optimaux avec Spring et RestTemplate, j'ai trouvé une meilleure solution: Smiley HTTP Proxy Servlet . L'avantage est qu'il fait vraiment du proxy AS-IS, tout comme le mod_proxy, et il le fait de manière en continu, sans mettre en cache la demande/réponse complète dans la mémoire.

Simplement, vous enregistrez une nouvelle servlet sur le chemin que vous souhaitez proxy vers un autre serveur et donnez à cette servlet l'hôte cible comme paramètre init. Si vous utilisez une application Web traditionnelle avec un fichier web.xml, vous pouvez la configurer comme suit:

<servlet>
    <servlet-name>proxy</servlet-name>
    <servlet-class>org.mitre.dsmiley.httpproxy.ProxyServlet</servlet-class>
    <init-param>
      <param-name>targetUri</param-name>
      <param-value>http://target.uri/target.path</param-value>
    </init-param>
</servlet>
<servlet-mapping>
  <servlet-name>proxy</servlet-name>
  <url-pattern>/mapping-path/*</url-pattern>
</servlet-mapping>

ou, bien sûr, vous pouvez utiliser la config d'annotation .

Si vous utilisez Spring Boot, c'est encore plus simple: il vous suffit de créer un bean de type ServletRegistrationBean, avec la configuration requise:

@Bean
public ServletRegistrationBean proxyServletRegistrationBean() {
    ServletRegistrationBean bean = new ServletRegistrationBean(
            new ProxyServlet(), "/mapping-path/*");
    bean.addInitParameter("targetUri", "http://target.uri/target.path");
    return bean;
}

De cette façon, vous pouvez également utiliser les propriétés Spring disponibles dans l'environnement.

Vous pouvez même étendre la classe ProxyServlet et remplacer ses méthodes pour personnaliser les en-têtes de demande/réponse, etc., au cas où vous en auriez besoin.

Mise à jour : Après avoir utilisé le servlet proxy de Smiley pendant un certain temps, nous avons eu des problèmes de délai d'attente, cela ne fonctionnait pas de manière fiable. Passé à Zuul à partir de Netflix, n'a plus eu de problème après cela. Un tutoriel sur sa configuration avec Spring Boot se trouve sur ce lien .

11
Utku Özdemir
@RequestMapping(value = "/**")
public ResponseEntity route(HttpServletRequest request) throws IOException {
    String body = IOUtils.toString(request.getInputStream(), Charset.forName(request.getCharacterEncoding()));
    try {
        ResponseEntity<Object> exchange = restTemplate.exchange(firstUrl + request.getRequestURI(),
                HttpMethod.valueOf(request.getMethod()),
                new HttpEntity<>(body),
                Object.class,
                request.getParameterMap());
        return exchange;
    } catch (final HttpClientErrorException e) {
        return new ResponseEntity<>(e.getResponseBodyAsByteArray(), e.getResponseHeaders(), e.getStatusCode());
    }
}
6
gstackoverflow

Si vous êtes obligé d'utiliser Spring, veuillez vérifier le échange de méthode de modèle de repos pour envoyer des requêtes proxy à un service tiers.

Ici vous pouvez trouver un exemple de travail.

1
db80