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!
Faça parte da comunidade!
Receba os melhores conteúdos sobre Go, Kubernetes, arquitetura de software, Cloud e esteja sempre atualizado com as tendências e práticas do mercado.
Livros Recomendados
Abaixo listei alguns dos melhores livros que já li sobre GO.




