Debugging requests with cURL

For more than one time I had to debug HTTP request or response headers and other details. To do that, I use two techniques, both based on cURL library. I explain them ahead.

Technique #1: From the command line

This is the easiest way to debug. It doesn’t require writing any actual code. Just call curl program from the command line, as usual, adding a new param: -vvv. This will enable the highest verbosity level.

$ curl -vvv http://google.com
* Rebuilt URL to: http://google.com/
* Trying 2800:3f0:4001:802::200e...
* Connected to google.com (2800:3f0:4001:802::200e) port 80 (#0)
> GET / HTTP/1.1
> Host: google.com
> User-Agent: curl/7.43.0
> Accept: */*
> 
< HTTP/1.1 302 Found
< Cache-Control: private
< Content-Type: text/html; charset=UTF-8
< Location: http://www.google.com.br/?gfe_rd=cr&ei=bUG8V53JGcvK8gfp3L-YBg
< Content-Length: 262
< Date: Tue, 23 Aug 2016 12:28:29 GMT
< 
<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>302 Moved</TITLE></HEAD><BODY>
<H1>302 Moved</H1>
The document has moved
<A HREF="http://www.google.com.br/?gfe_rd=cr&amp;ei=bUG8V53JGcvK8gfp3L-YBg">here</A>.
</BODY></HTML>
* Connection #0 to host google.com left intact

As you can see in the example above, it outputs all request and response info.

It’s possible to output everything to a file, by adding > output_file.txt to the end of the command. Using our previous call:

$ curl -vvv http://google.com > output.txt

Well, one may now ask: if this is so easy, why do you have a second way to debug request? Following we’ll see why that.

Technique #2: From a PHP script

I’ve written on debugging cURL and PHP at Kettle.io Blog. Let’s say you have to send a dynamic header with the request, like a JWT authorization token. It’s not impossible to that from the command line, but it’s easier using programming. For those cases, I use the cURL PHP extension. Check out the script below.

$url = 'http://google.com';
$headers = [
    'Accept' => 'application/json',
];

/*
 * We're going to use the output buffer to store the debug info.
 */
ob_start();
$out = fopen('php://output', 'w');

$handler = curl_init($url);

/*
 * Here we set the library verbosity and redirect the error output to the 
 * output buffer.
 */
curl_setopt($handler, CURLOPT_VERBOSE, true);
curl_setopt($handler, CURLOPT_STDERR, $out);

$requestHeaders = [];
foreach ($headers as $k => $v) {
    $requestHeaders[] = $k . ': ' . $v;
}
curl_setopt($handler, CURLOPT_HTTPHEADER, $requestHeaders);
curl_setopt($handler, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($handler);
fclose($out);

/*
 * Joining debug info and response body.
 */
$data = ob_get_clean();
$data .= PHP_EOL . $response . PHP_EOL;
echo $data;

Now, you can customize this code to add some dynamic data to a header or any other request part. After doing that, run it using the PHP program from the command line:

$ php curldebug.php

P.S.: I’m assuming that you saved the script as curldebug.php.

As we did with the curl program, it’s possible to output everything to a file. Just append the > output_file.txt to the call.

Conclusion

Debugging requests can be a lifesaver when dealing with third-party APIs and other services. Headers may contain helpful info to find what is going wrong with that weird response body.

Multiple Domain plugin for WordPress

For a few projects in the past, I had to find a way to make WordPress work with more than one domain. There are a couple plugins to do that, but they are outdated and/or don’t work well. So, I wrote this plugin.

Check this out: https://wordpress.org/plugins/multiple-domain/

Multiple Domain allows you having more than one domain in a single WordPress installation. This plugin doesn’t support more than one theme or advanced customizations for each domain. It’s only intended to enable constant navigation under many domains. For a more complex setup, there is WordPress Multisite (MU).

When there is more than one domain set in your host, all links and resources will point to the default domain. This is the default WordPress behavior. With Multiple Domain installed and properly configured, it’ll update all link on the fly. This way, the user navigation will be end-to-end under the same domain.

You can also set an optional base URL. If you want only a set of URL’s available under a given domain, you can use this restriction.

Photo credit: Roya Ann Miller

Forms no Symfony2 : Validando apenas os campos submetidos

No último post, sobre CRUDs RESTful com Symfony2, uma das características do código de exemplo era usar o método PUT na atualização do objeto. Seguindo isso, estive fazendo algumas exeperiências com o mesmo form do Symfony na criação e atualização. No caso do update, gostaria de enviar apenas os campos a serem alterados (em um objeto que tenha ‘id‘, ‘title‘, ‘description‘ e ‘date‘ como atributos, por exemplo), assim:

$ curl -X PUT -d "title=Testando a atualizacao" http://localhost:8000/api/resources/123

Então descobri que o método que substitui o bind a partir da versão 2.3 do Symfony, tem um segundo parâmetro para indicar se os campos não enviados devem ou não ser limpos, se não forem, terão o valor do objeto original mantido:

$clearMissing = 'PUT' !== $request->getMethod();
$form->submit($request, $clearMissing);

Simples assim!

Escrevendo um CRUD RESTful com Symfony2

Antes de começar, a intenção desse post não é incentivar ninguém a reinventar a roda, mas mostrar como as características de roteamento do Symfony podem ser usadas para manipular métodos HTTP. Eu, particularmente, não cheguei a experimentar, mas existe um bundle bastante mencionado que promete facilitar a vida de quem quer desenvolver webservices REST em cima do Symfony2: o FOSRestBundle. Esclarecido isso, vamos lá!

Para esse CRUD, vou usar a seguinte convenção:

  • GET /api/resources/ — buscar todos os objetos do nosso recurso;
  • POST /api/resources/ — criar um novo objeto;
  • GET /api/resources/123/ — buscar um objeto específico, de acordo com o código informado;
  • PUT /api/resources/123/ — atualizar um objeto;
  • DELETE /api/resources/123/ — excluir um objeto existente.

Definido isso, podemos escrever controller abaixo. Ah! Estou omitindo vários detalhes de lógica relacionados à busca e persistência das informações na camada de modelo (M do MVC) para facilitar o entendimento.

<?php

namespace Acme\DemoBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;

/**
 *
 * @Route("/api/resources")
 */
class ApiController extends Controller
{

    /**
     *
     * @param \Symfony\Component\HttpFoundation\Request $request
     * @return array|\Symfony\Component\HttpFoundation\Response
     *
     * @Route("/", name="resources_api_index")
     * @Method("GET")
     */
    public function indexAction(Request $request)
    {
        // ...

        return $this->createJsonResponse(array(
            'resources' => $resources,
        ));
    }

    /**
     *
     * @param \Symfony\Component\HttpFoundation\Request $request
     * @return array|\Symfony\Component\HttpFoundation\Response
     *
     * @Route("/", name="resources_api_create")
     * @Method("POST")
     */
    public function createAction(Request $request)
    {

        // ...

        if (!$success) { // Algum erro de validação, de comunicação com a camada de persistência etc.
            return $this->createJsonResponse(array(
                'error' => 'Não foi possível criar o objeto. Detalhes: ...',
            ), 400);
        }

        return $this->createJsonResponse(array(
            'resource' => $resource,
        ), 201);
    }

    /**
     *
     * @param int $id
     * @return array|\Symfony\Component\HttpFoundation\Response
     *
     * @Route("/{id}", name="resources_api_retrieve")
     * @Method("GET")
     */
    public function retrieveAction($id = 0)
    {
        $id = (int) $id;

        // ...

        if (null === $resource) {
            return $this->createJsonResponse(array(
                'error' => 'Recurso não encontrado. Detalhes: ...',
            ), 404);
        }

        return $this->createJsonResponse(array(
            'resource' => $resource,
        ));
    }

    /**
     *
     * @param int $id
     * @return array|\Symfony\Component\HttpFoundation\Response
     *
     * @Route("/{id}", name="resource_api_update")
     * @Method("PUT")
     */
    public function updateAction($id = 0)
    {
        $id = (int) $id;

        // ...

        if (null === $resource) {
            return $this->createJsonResponse(array(
                'error' => 'Recurso não encontrado. Detalhes: ...',
            ), 404);
        }

        // ...

        if (!$success) { // Algum erro de validação, de comunicação com a camada de persistência etc.
            return $this->createJsonResponse(array(
                'error' => 'Não foi possível criar o objeto. Detalhes: ...',
            ), 400);
        }

        return $this->createJsonResponse(array(
            'resource' => $resource,
        ));
    }

    /**
     *
     * @param int $id
     * @return array|\Symfony\Component\HttpFoundation\Response
     *
     * @Route("/{id}", defaults={"id"=null}, name="resources_api_delete")
     * @Method("DELETE")
     */
    public function deleteAction($id = 0)
    {
        $id = (int) $id;

        // ...

        if (null === $resource) {
            return $this->createJsonResponse(array(
                'error' => 'Recurso não encontrado. Detalhes: ...',
            ), 404);
        }

        // ...

        return $this->createJsonResponse(array(
            'success' => 1,
        ));
    }

    /**
     *
     * @param array $data
     * @param int $code
     * @param array $headers
     * @return \Symfony\Component\HttpFoundation\Response
     */
    protected function createJsonResponse(array $data, $code = 200, array $headers = array())
    {
        $response = new Response(json_encode($data), $code, $headers);
        $response->headers->set('Content-type', 'application/json');

        return $response;
    }

}

Os pontos que merecem antenção especial no nosso controller de exemplo são os seguintes:

@Route e @Method

As rotas se repetem entre as diferentes ações (/api/resources/ e /api/resources/{id}), o que faz com que o Symfony direcione para a ação correta é o método HTTP (GET, POST, PUT ou DELETE).

Códigos HTTP

No retorno, além do código padrão para sucesso — 200, amplamente usado — retornamos:

  • 201, para quando um novo objeto foi criado;
  • 400, para os casos em que há algum erro “capturável” no processamento do request; e
  • 404, para quando um objeto não foi encontrado.

Referências

Gerando URLs absolutas para rotas do Symfony2

Nas classes que extendem, em algum nível, a classe Controller padrão do Symfony2 está disponível um método utilitário que é simplesmente um atalho para o gerador de rotas do serviço de roteamento (router). Esse método é o generateUrl($route, $parameters, $referenceType).

Na maioria dos casos de uso, como em redirects, passamos apenas um ou dois parâmetros para esse método, que são, respectivamente, o nome da rota e, opcionalmente, os parâmetros que devem ser aplicados a ela. O terceiro parâmetro, que acaba esquecido, traz algumas possibilidades que podem ser muito úteis. Um exemplo prático: precisei incluir na resposta de um webservice em JSON a URL para uma página da aplicação relacionada a objetos específicos, como esse serviço seria consumido por uma aplicativo mobile, precisava retornar a URL absoluta — incluindo o protocolo e domínio — e não apenas o caminho, que é o comportamento padrão do método. Para fazer isso, bastou passar a constante abaixo no terceiro parâmetro do método:

\Symfony\Component\Routing\Generator\UrlGeneratorInterface::ABSOLUTE_URL

Para dar mais legibilidade ao código, adicionei um use para a interface:

use Symfony\Component\Routing\Generator\UrlGeneratorInterface;

// ...

class MainController extends Controller
{
  public function indexAction()
  {
    // ...
    $item['Url'] = $this->generateUrl('item_detail', array('id' => $item['Id']), UrlGeneratorInterface::ABSOLUTE_URL);
  }
}

Checking if a form was sent via POST method

Since I started programming with PHP I’ve used several ways to check if a form was sent via the POST method. This is useful when the same method of control (or the same script, if you’re using structured programming or not using the MVC model) handle the form presentation and also its submission.

I used three ways, in chronological order, to do this: Continue reading “Checking if a form was sent via POST method”