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.

151 lines
4.0 KiB

// Copyright 2013 gopm 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 doc
import (
"encoding/json"
"flag"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"time"
"github.com/astaxie/beego"
)
var userAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1541.0 Safari/537.36"
var (
dialTimeout = flag.Duration("dial_timeout", 10*time.Second, "Timeout for dialing an HTTP connection.")
requestTimeout = flag.Duration("request_timeout", 20*time.Second, "Time out for roundtripping an HTTP request.")
)
func timeoutDial(network, addr string) (net.Conn, error) {
return net.DialTimeout(network, addr, *dialTimeout)
}
type transport struct {
t http.Transport
}
func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) {
timer := time.AfterFunc(*requestTimeout, func() {
t.t.CancelRequest(req)
beego.Warn("Canceled request for %s", req.URL)
})
defer timer.Stop()
resp, err := t.t.RoundTrip(req)
return resp, err
}
var (
httpTransport = &transport{t: http.Transport{Dial: timeoutDial, ResponseHeaderTimeout: *requestTimeout / 2}}
HttpClient = &http.Client{Transport: httpTransport}
)
// httpGet 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
}
p, err := ioutil.ReadAll(rc)
rc.Close()
return p, 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) {
req, err := http.NewRequest("GET", url, nil)
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, &RemoteError{req.URL.Host, 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 = NotFoundError{"Resource not found: " + url}
} else {
err = &RemoteError{req.URL.Host, fmt.Errorf("get %s -> %d", url, resp.StatusCode)}
}
return nil, err
}
// fetchFiles fetches the source files specified by the rawURL field in parallel.
func fetchFiles(client *http.Client, files []*source, header http.Header) error {
ch := make(chan error, len(files))
for i := range files {
go func(i int) {
req, err := http.NewRequest("GET", files[i].rawURL, nil)
if err != nil {
ch <- err
return
}
req.Header.Set("User-Agent", userAgent)
for k, vs := range header {
req.Header[k] = vs
}
resp, err := client.Do(req)
if err != nil {
ch <- &RemoteError{req.URL.Host, err}
return
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
ch <- &RemoteError{req.URL.Host, fmt.Errorf("get %s -> %d", req.URL, resp.StatusCode)}
return
}
files[i].data, err = ioutil.ReadAll(resp.Body)
if err != nil {
ch <- &RemoteError{req.URL.Host, err}
return
}
ch <- nil
}(i)
}
for _ = range files {
if err := <-ch; err != nil {
return err
}
}
return nil
}
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 {
err = NotFoundError{"JSON syntax error at " + url}
}
return err
}