view of landslide

Como reutilizar o response body de uma request HTTP

Algumas semanas atrás, enquanto eu estava desenvolvendo um web crawler junto com um dos alunos de mentoria aqui do Aprenda Golang, nos deparamos com um problema onde precisávamos fazer o parse do resp.Body duas vezes, sendo a primeira para salvar o body no banco de dados e a segunda para extrair os links do página.

Para ficar mais claro, vamos ver o código abaixo.

// Executa a request
resp, err := http.Get(website)
if err != nil {
	log.Println("ERROR:", err)
	return
}
defer resp.Body.Close()

// Extrai conteúdo para salvar no banco de dados
content, err := ioutil.ReadAll(resp.Body)
if err != nil {
	panic(err)
}

// Retorna uma struct para extrair os links sem regex
body, err := html.Parse(resp.Body)
if err != nil {
	panic(err)
}

O problema? Somente a função ioutil.ReadAll funcionava.

No inicio eu achei que fosse algum bug da linguagem. Porém depois de muita pesquisa, descobri que não era.

De forma bem simplista, após você ler o resp.Body ele fica “vazio”.

Como tanto o ioutil.ReadAll, quanto o html.Parse esperam uma interface do tipo io.Reader, eu não conseguia utilizar o retorna de uma delas para executar a outra. E como após a chamada de uma delas o resp.Body ficava “vazio”, só era possível executar uma delas.

Depois de pesquisar sobre o assunto, vi que existe uma forma para “resetar” o resp.Body para seu estado “não lido”.

// Executa a request
resp, err := http.Get(website)
if err != nil {
	log.Println("ERROR:", err)
	return
}
defer resp.Body.Close()

// Extrai conteúdo para salvar no banco de dados
content, err := ioutil.ReadAll(resp.Body)
if err != nil {
	panic(err)
}

// Reseta o resp.Body para seu estado "não lido"
resp.Body = io.NopCloser(bytes.NewBuffer(content))

// Retorna uma struct para extrair os links sem regex
body, err := html.Parse(resp.Body)
if err != nil {
	panic(err)
}

Como a função bytes.NewBuffer não retorna uma struct que implemente a interface io.ReadCloser, precisamos utilizar a função io.NopCloser em conjunto para conseguir “resetar” o estado do resp.Body.

Por curiosidade, como podemos ver abaixo, a implementação dessa função é bem simples.

func NopCloser(r Reader) ReadCloser {
	if _, ok := r.(WriterTo); ok {
		return nopCloserWriterTo{r}
	}
	return nopCloser{r}
}

type nopCloser struct {
	Reader
}

func (nopCloser) Close() error { return nil }

Ela basicamente retorna uma struct que serve como um wrapper para a struct que implementa a interface io.Reader, adicionando um método Close que não faz nada, ou seja, “sem nenhuma operação”… no operation… no-op… NopCloser.

Depois dessa pequena mudança, conseguimos reutilizar o resp.Body e executar as duas funções que queríamos.

Deixe suas dúvidas nos comentários.

Até a próxima!


Subscreva

Fique por dentro de tudo o que acontece no mundo Go.

Deixe uma resposta