1
0
mirror of https://github.com/v2fly/v2ray-core.git synced 2025-01-30 21:26:33 -05:00
v2fly/proxy/speedtest/speedtest.go
Darien Raymond a21d769e37
speedtest
2018-07-25 19:22:36 +02:00

182 lines
4.7 KiB
Go

package speedtest
//go:generate go run $GOPATH/src/v2ray.com/core/common/errors/errorgen/main.go -pkg speedtest -path Proxy,SpeedTest
import (
"bufio"
"context"
"io"
"net/http"
"strconv"
"strings"
"v2ray.com/core"
"v2ray.com/core/common"
"v2ray.com/core/common/buf"
"v2ray.com/core/common/serial"
"v2ray.com/core/common/session"
"v2ray.com/core/proxy"
)
var rndBytes = []byte("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%&\\'()*+,-")
type rndBytesReader struct{}
func (rndBytesReader) Read(b []byte) (int, error) {
totalBytes := 0
for totalBytes < len(b) {
nBytes := copy(b[totalBytes:], rndBytes[:])
totalBytes += nBytes
}
return totalBytes, nil
}
func (rndBytesReader) Close() error {
return nil
}
type SpeedTestHandler struct{}
func New(ctx context.Context, config *Config) (*SpeedTestHandler, error) {
return &SpeedTestHandler{}, nil
}
type noOpCloser struct {
io.Reader
}
func (c *noOpCloser) Close() error {
return nil
}
func defaultResponse() *http.Response {
response := &http.Response{
Status: "Not Found",
StatusCode: 404,
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
Header: http.Header(make(map[string][]string)),
Body: nil,
ContentLength: 0,
Close: true,
}
response.Header.Set("Content-Type", "text/plain; charset=UTF-8")
return response
}
func (h *SpeedTestHandler) Process(ctx context.Context, link *core.Link, dialer proxy.Dialer) error {
reader := link.Reader
writer := link.Writer
defer func() {
common.Close(writer)
}()
bufReader := bufio.NewReader(&buf.BufferedReader{
Reader: reader,
Direct: true,
})
bufWriter := buf.NewBufferedWriter(writer)
common.Must(bufWriter.SetBuffered(false))
request, err := http.ReadRequest(bufReader)
if err != nil {
return newError("failed to read speedtest request").Base(err)
}
path := strings.ToLower(request.URL.Path)
if len(path) > 0 && path[0] == '/' {
path = path[1:]
}
switch path {
case "hello":
respBody := "hello 2.5 2017-08-15.1314.4ae12d5"
response := &http.Response{
Status: "OK",
StatusCode: 200,
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
Header: http.Header(make(map[string][]string)),
Body: &noOpCloser{strings.NewReader(respBody)},
ContentLength: int64(len(respBody)),
Close: true,
}
response.Header.Set("Content-Type", "text/plain; charset=UTF-8")
return response.Write(bufWriter)
case "upload":
switch strings.ToUpper(request.Method) {
case "POST":
var sc buf.SizeCounter
buf.Copy(buf.NewReader(request.Body), buf.Discard, buf.CountSize(&sc)) // nolint: errcheck
response := &http.Response{
Status: "OK",
StatusCode: 200,
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
Header: http.Header(make(map[string][]string)),
Body: &noOpCloser{strings.NewReader(serial.Concat("size=", sc.Size))},
Close: true,
}
response.Header.Set("Content-Type", "text/plain; charset=UTF-8")
return response.Write(bufWriter)
case "OPTIONS":
response := &http.Response{
Status: "OK",
StatusCode: 200,
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
Header: http.Header(make(map[string][]string)),
Body: nil,
ContentLength: 0,
Close: true,
}
response.Header.Set("Content-Type", "text/plain; charset=UTF-8")
response.Header.Set("Connection", "Close")
response.Header.Set("Access-Control-Allow-Methods", "OPTIONS, POST")
response.Header.Set("Access-Control-Allow-Headers", "content-type")
response.Header.Set("Access-Control-Allow-Origin", "http://www.speedtest.net")
return response.Write(bufWriter)
default:
newError("unknown method for upload: ", request.Method).WriteToLog(session.ExportIDToError(ctx))
return defaultResponse().Write(bufWriter)
}
case "download":
query := request.URL.Query()
sizeStr := query.Get("size")
size, err := strconv.Atoi(sizeStr)
if err != nil {
return defaultResponse().Write(bufWriter)
}
response := &http.Response{
Status: "OK",
StatusCode: 200,
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
Header: http.Header(make(map[string][]string)),
Body: rndBytesReader{},
ContentLength: int64(size),
Close: true,
}
response.Header.Set("Content-Type", "text/plain; charset=UTF-8")
return response.Write(bufWriter)
default:
newError("unknown path: ", path).WriteToLog(session.ExportIDToError(ctx))
return defaultResponse().Write(bufWriter)
}
}
func init() {
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
return New(ctx, config.(*Config))
}))
}