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!