You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
201 lines
5.2 KiB
201 lines
5.2 KiB
// Copyright 2013 com authors
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
|
// not use this file except in compliance with the License. You may obtain
|
|
// a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
// License for the specific language governing permissions and limitations
|
|
// under the License.
|
|
|
|
package com
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"os"
|
|
"path"
|
|
)
|
|
|
|
type NotFoundError struct {
|
|
Message string
|
|
}
|
|
|
|
func (e NotFoundError) Error() string {
|
|
return e.Message
|
|
}
|
|
|
|
type RemoteError struct {
|
|
Host string
|
|
Err error
|
|
}
|
|
|
|
func (e *RemoteError) Error() string {
|
|
return e.Err.Error()
|
|
}
|
|
|
|
var UserAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1541.0 Safari/537.36"
|
|
|
|
// HttpCall makes HTTP method call.
|
|
func HttpCall(client *http.Client, method, url string, header http.Header, body io.Reader) (io.ReadCloser, error) {
|
|
req, err := http.NewRequest(method, url, body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
req.Header.Set("User-Agent", UserAgent)
|
|
for k, vs := range header {
|
|
req.Header[k] = vs
|
|
}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if resp.StatusCode == 200 {
|
|
return resp.Body, nil
|
|
}
|
|
resp.Body.Close()
|
|
if resp.StatusCode == 404 { // 403 can be rate limit error. || resp.StatusCode == 403 {
|
|
err = fmt.Errorf("resource not found: %s", url)
|
|
} else {
|
|
err = fmt.Errorf("%s %s -> %d", method, url, resp.StatusCode)
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
// HttpGet gets the specified resource.
|
|
// ErrNotFound is returned if the server responds with status 404.
|
|
func HttpGet(client *http.Client, url string, header http.Header) (io.ReadCloser, error) {
|
|
return HttpCall(client, "GET", url, header, nil)
|
|
}
|
|
|
|
// HttpPost posts the specified resource.
|
|
// ErrNotFound is returned if the server responds with status 404.
|
|
func HttpPost(client *http.Client, url string, header http.Header, body []byte) (io.ReadCloser, error) {
|
|
return HttpCall(client, "POST", url, header, bytes.NewBuffer(body))
|
|
}
|
|
|
|
// HttpGetToFile gets the specified resource and writes to file.
|
|
// ErrNotFound is returned if the server responds with status 404.
|
|
func HttpGetToFile(client *http.Client, url string, header http.Header, fileName string) error {
|
|
rc, err := HttpGet(client, url, header)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer rc.Close()
|
|
|
|
os.MkdirAll(path.Dir(fileName), os.ModePerm)
|
|
f, err := os.Create(fileName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
_, err = io.Copy(f, rc)
|
|
return err
|
|
}
|
|
|
|
// HttpGetBytes gets the specified resource. ErrNotFound is returned if the server
|
|
// responds with status 404.
|
|
func HttpGetBytes(client *http.Client, url string, header http.Header) ([]byte, error) {
|
|
rc, err := HttpGet(client, url, header)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rc.Close()
|
|
return ioutil.ReadAll(rc)
|
|
}
|
|
|
|
// HttpGetJSON gets the specified resource and mapping to struct.
|
|
// ErrNotFound is returned if the server responds with status 404.
|
|
func HttpGetJSON(client *http.Client, url string, v interface{}) error {
|
|
rc, err := HttpGet(client, url, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer rc.Close()
|
|
err = json.NewDecoder(rc).Decode(v)
|
|
if _, ok := err.(*json.SyntaxError); ok {
|
|
return fmt.Errorf("JSON syntax error at %s", url)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// HttpPostJSON posts the specified resource with struct values,
|
|
// and maps results to struct.
|
|
// ErrNotFound is returned if the server responds with status 404.
|
|
func HttpPostJSON(client *http.Client, url string, body, v interface{}) error {
|
|
data, err := json.Marshal(body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
rc, err := HttpPost(client, url, http.Header{"content-type": []string{"application/json"}}, data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer rc.Close()
|
|
err = json.NewDecoder(rc).Decode(v)
|
|
if _, ok := err.(*json.SyntaxError); ok {
|
|
return fmt.Errorf("JSON syntax error at %s", url)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// A RawFile describes a file that can be downloaded.
|
|
type RawFile interface {
|
|
Name() string
|
|
RawUrl() string
|
|
Data() []byte
|
|
SetData([]byte)
|
|
}
|
|
|
|
// FetchFiles fetches files specified by the rawURL field in parallel.
|
|
func FetchFiles(client *http.Client, files []RawFile, header http.Header) error {
|
|
ch := make(chan error, len(files))
|
|
for i := range files {
|
|
go func(i int) {
|
|
p, err := HttpGetBytes(client, files[i].RawUrl(), nil)
|
|
if err != nil {
|
|
ch <- err
|
|
return
|
|
}
|
|
files[i].SetData(p)
|
|
ch <- nil
|
|
}(i)
|
|
}
|
|
for _ = range files {
|
|
if err := <-ch; err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// FetchFilesCurl uses command `curl` to fetch files specified by the rawURL field in parallel.
|
|
func FetchFilesCurl(files []RawFile, curlOptions ...string) error {
|
|
ch := make(chan error, len(files))
|
|
for i := range files {
|
|
go func(i int) {
|
|
stdout, _, err := ExecCmd("curl", append(curlOptions, files[i].RawUrl())...)
|
|
if err != nil {
|
|
ch <- err
|
|
return
|
|
}
|
|
|
|
files[i].SetData([]byte(stdout))
|
|
ch <- nil
|
|
}(i)
|
|
}
|
|
for _ = range files {
|
|
if err := <-ch; err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|