2

How to intercept a request and send it with HttpClient to another server and return the response from that server after some modification?

The original request will contain data and files. I am looking for the least verbose solution, up to copying the original raw body as a string. The part I am stuck with is plugging the Request object to the HttpClient request in the shortest way (without writing code to copy data and headers bit by bit), the rest (handling the response) I will manage.

yivi
  • 42,438
  • 18
  • 116
  • 138
Jacek Krysztofik
  • 1,266
  • 1
  • 13
  • 29

2 Answers2

5

Symfony's Http Client does not accept an HttpFoundation\Request as a parameter to make new requests.

You'll need to convert the Request object into the necessary parameters (including converting the path, method, passing appropriate headers, etc) "manually".

Then, you'd have to create the an HttpFoundation\Response based on the client's response (which, again, cannot simply be sent back in your controller).

Depending on your specific requirements, it's not particularly onerous. A naive, probably buggy implementation would be something like:

use Symfony\Component\HttpFoundation;
use Symfony\Contracts\HttpClient\HttpClientInterface;

class FooProxy
{
    public function __construct(private HttpClientInterface $client)
    {
    }

    public function __invoke(HttpFoundation\Request $request): HttpFoundation\Response
    {
        $options            = [];
        $newServer          = 'https://www.example.com';
        $options['headers'] = $request->headers->all();
        $options['body']    = $request->getContent();

        $clientResponse = $this->client->request(
            $request->getMethod(),
            $newServer . $request->getRequestUri(),
            $options
        );

        return new HttpFoundation\Response(
            $clientResponse->getContent(false),
            $clientResponse->getStatusCode(),
            $clientResponse->getHeaders(false)
        );

    }
}

Again, this is very naive implementation. You'd need to make sure that you deal with any exceptions and edge cases.

Take into account that PHP is not a great choice of language to build a real HTTP proxy.

yivi
  • 42,438
  • 18
  • 116
  • 138
  • Thanks. It's what I did (although 1. files won't be copied in `getContent`, 2. the request will only form correctly if I construct it with `Mime\FormDataPart` and export body as string). For that reason I think it is silly that the `HttpFoundation\Request` cannot be plugged directly into `HttpClient`. Why not use the same interface, when it even has special methods to clone and modify the original request? I don't understand it and I don't like it. – Jacek Krysztofik Jul 19 '21 at 09:01
0

I did use the answer from @yivi.
But After a while I encounter the error mentioned by @Jacek Krysztofik.

After digging a while, I finnaly find the solution in the Symfony documentation.

Here is the corrected code:

use Symfony\Component\HttpFoundation;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Component\Mime\Part\Multipart\FormDataPart;

class FooProxy
{
    public function __construct(private HttpClientInterface $client)
    {
    }

    public function __invoke(HttpFoundation\Request $request): HttpFoundation\Response
    {
        $options            = [];
        $newServer          = 'https://www.example.com';
        $options['headers'] = $request->headers->all();
        $options['body']    = $request->getContent();

        if ($request->request->count()) {
            // This is a post request, update the body and headers accordingly
            $formData = new FormDataPart($request->request->all());
            $options['headers'] = array_merge($options['headers'], $formData->getPreparedHeaders()->toArray());
            $options['body'] = $formData->bodyToIterable();
        }

        $clientResponse = $this->client->request(
            $request->getMethod(),
            $newServer . $request->getRequestUri(),
            $options
        );

        return new HttpFoundation\Response(
            $clientResponse->getContent(false),
            $clientResponse->getStatusCode(),
            $clientResponse->getHeaders(false)
        );

    }
}

It's proabbly still not 100% bulletproof, but in my case it do the job for get and post requests.