mirror of https://github.com/hunshcn/gh-proxy
hunshcn
2 years ago
commit
bdab7ddd67
5 changed files with 277 additions and 0 deletions
@ -0,0 +1,21 @@
|
||||
MIT License |
||||
|
||||
Copyright (c) 2020 hunshcn |
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
of this software and associated documentation files (the "Software"), to deal |
||||
in the Software without restriction, including without limitation the rights |
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
copies of the Software, and to permit persons to whom the Software is |
||||
furnished to do so, subject to the following conditions: |
||||
|
||||
The above copyright notice and this permission notice shall be included in all |
||||
copies or substantial portions of the Software. |
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
SOFTWARE. |
@ -0,0 +1,109 @@
|
||||
'use strict' |
||||
|
||||
/** @type {RequestInit} */ |
||||
const PREFLIGHT_INIT = { |
||||
status: 204, |
||||
headers: new Headers({ |
||||
'access-control-allow-origin': '*', |
||||
'access-control-allow-methods': 'GET,POST,PUT,PATCH,TRACE,DELETE,HEAD,OPTIONS', |
||||
'access-control-max-age': '1728000', |
||||
}), |
||||
} |
||||
|
||||
const PREFIX = 'https://example.com/' |
||||
|
||||
|
||||
/** |
||||
* @param {any} body |
||||
* @param {number} status |
||||
* @param {Object<string, string>} headers |
||||
*/ |
||||
function makeRes(body, status = 200, headers = {}) { |
||||
headers['access-control-allow-origin'] = '*' |
||||
return new Response(body, {status, headers}) |
||||
} |
||||
|
||||
|
||||
addEventListener('fetch', e => { |
||||
const ret = fetchHandler(e) |
||||
.catch(err => makeRes('cfworker error:\n' + err.stack, 502)) |
||||
e.respondWith(ret) |
||||
}) |
||||
|
||||
|
||||
/** |
||||
* @param {FetchEvent} e |
||||
*/ |
||||
async function fetchHandler(e) { |
||||
const req = e.request |
||||
const urlStr = req.url |
||||
const urlObj = new URL(urlStr) |
||||
let path = urlStr.substring(urlObj.origin.length + 1).replace(/^(https?):\/+/, '$1://') |
||||
return httpHandler(req, path) |
||||
} |
||||
|
||||
|
||||
/** |
||||
* @param {Request} req |
||||
* @param {string} pathname |
||||
*/ |
||||
function httpHandler(req, pathname) { |
||||
const reqHdrRaw = req.headers |
||||
|
||||
// preflight
|
||||
if (req.method === 'OPTIONS' && |
||||
reqHdrRaw.has('access-control-request-headers') |
||||
) { |
||||
return new Response(null, PREFLIGHT_INIT) |
||||
} |
||||
|
||||
const reqHdrNew = new Headers(reqHdrRaw) |
||||
|
||||
/** @type {RequestInit} */ |
||||
const reqInit = { |
||||
method: req.method, |
||||
headers: reqHdrNew, |
||||
redirect: 'manual', |
||||
body: req.body |
||||
} |
||||
return proxy(pathname, reqInit) |
||||
} |
||||
|
||||
|
||||
/** |
||||
* |
||||
* @param {string} path |
||||
* @param {RequestInit} reqInit |
||||
*/ |
||||
async function proxy(path, reqInit) { |
||||
const res = await fetch(path, reqInit) |
||||
const resHdrOld = res.headers |
||||
const resHdrNew = new Headers(resHdrOld) |
||||
|
||||
const status = res.status |
||||
|
||||
if (resHdrNew.has('location')) { |
||||
let _location = resHdrNew.get('location') |
||||
resHdrNew.set('location', PREFIX + _location) |
||||
} |
||||
resHdrNew.set('access-control-expose-headers', '*') |
||||
resHdrNew.set('access-control-allow-origin', '*') |
||||
|
||||
resHdrNew.delete('content-security-policy') |
||||
resHdrNew.delete('content-security-policy-report-only') |
||||
resHdrNew.delete('clear-site-data') |
||||
if (path.substring(0, path.lastIndexOf("/")).endsWith('/info/lfs/objects')) { |
||||
let text = await res.text() |
||||
text = text.replaceAll('"href":"https://', '"href":"' + PREFIX + 'https://') |
||||
resHdrNew.delete("content-length") |
||||
return new Response(text, { |
||||
status, |
||||
headers: resHdrNew, |
||||
}) |
||||
} |
||||
|
||||
return new Response(res.body, { |
||||
status, |
||||
headers: resHdrNew, |
||||
}) |
||||
} |
@ -0,0 +1,144 @@
|
||||
package main |
||||
|
||||
import ( |
||||
"bytes" |
||||
"fmt" |
||||
"net/http" |
||||
"net/http/httputil" |
||||
"net/url" |
||||
"sort" |
||||
"strings" |
||||
) |
||||
|
||||
const HostPrefix = "http://127.0.0.1:8080/" |
||||
|
||||
func NewServer(enableLog bool) *Server { |
||||
director := func(req *http.Request) { |
||||
if _, ok := req.Header["User-Agent"]; !ok { |
||||
// explicitly disable User-Agent so it's not set to default value
|
||||
req.Header.Set("User-Agent", "") |
||||
} |
||||
} |
||||
return &Server{proxy: &httputil.ReverseProxy{Director: director}, enableLog: enableLog} |
||||
} |
||||
|
||||
type Server struct { |
||||
proxy *httputil.ReverseProxy |
||||
enableLog bool |
||||
} |
||||
|
||||
func (s Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { |
||||
targetUrl := r.URL.RequestURI() |
||||
targetUrl = targetUrl[1:] |
||||
target, err := url.Parse(targetUrl) |
||||
if err != nil { |
||||
w.WriteHeader(500) |
||||
return |
||||
} |
||||
r.Host = target.Host |
||||
r.URL = target |
||||
r.URL.Host = target.Host |
||||
r.URL.Path = target.Path |
||||
r.URL.RawPath = target.RawPath |
||||
mw := &MWriter{Next: s.proxy.ServeHTTP, EnableLog: s.enableLog} |
||||
|
||||
if strings.HasSuffix(targetUrl[:strings.LastIndexByte(targetUrl, '/')], "/info/lfs/objects") { |
||||
mw.ReplaceFunc = func(mw *MWriter) { |
||||
for k, v := range mw.cache.header { |
||||
if k == "Content-Length" { |
||||
continue |
||||
} |
||||
mw.writer.Header()[k] = v |
||||
} |
||||
if mw.cache.statusCode != 0 { |
||||
mw.writer.WriteHeader(mw.cache.statusCode) |
||||
} |
||||
content := mw.cache.body.String() |
||||
content = strings.ReplaceAll(content, "\"href\":\"https://", "\"href\":\""+HostPrefix+"https://") |
||||
_, _ = mw.writer.Write([]byte(content)) |
||||
} |
||||
} |
||||
mw.ServeHTTP(w, r) |
||||
} |
||||
|
||||
type MWriter struct { |
||||
writer http.ResponseWriter |
||||
request *http.Request |
||||
cache struct { |
||||
header http.Header |
||||
body bytes.Buffer |
||||
statusCode int |
||||
} |
||||
wroteLog bool |
||||
|
||||
Next func(http.ResponseWriter, *http.Request) |
||||
ReplaceFunc func(*MWriter) |
||||
EnableLog bool |
||||
} |
||||
|
||||
func (mw *MWriter) Log() { |
||||
if mw.EnableLog && !mw.wroteLog { |
||||
if mw.cache.statusCode == 0 { |
||||
mw.cache.statusCode = http.StatusOK |
||||
} |
||||
fmt.Println(mw.request.Method, mw.request.URL.String(), mw.cache.statusCode) |
||||
header := mw.writer.Header() |
||||
if mw.ReplaceFunc != nil { |
||||
header = mw.cache.header |
||||
} |
||||
var keys []string |
||||
for key := range header { |
||||
keys = append(keys, key) |
||||
} |
||||
sort.Strings(keys) |
||||
for _, key := range keys { |
||||
for _, value := range header[key] { |
||||
fmt.Printf("%s: %s\n", key, value) |
||||
} |
||||
} |
||||
fmt.Println() |
||||
mw.wroteLog = true |
||||
} |
||||
} |
||||
|
||||
func (mw *MWriter) Write(data []byte) (int, error) { |
||||
mw.Log() |
||||
if mw.ReplaceFunc == nil { |
||||
return mw.writer.Write(data) |
||||
} |
||||
return mw.cache.body.Write(data) |
||||
} |
||||
|
||||
func (mw *MWriter) WriteHeader(statusCode int) { |
||||
for i := range mw.Header()["Location"] { |
||||
mw.Header()["Location"][i] = HostPrefix + mw.Header()["Location"][0] |
||||
} |
||||
mw.Log() |
||||
if mw.ReplaceFunc == nil { |
||||
mw.writer.WriteHeader(statusCode) |
||||
} |
||||
mw.cache.statusCode = statusCode |
||||
} |
||||
|
||||
func (mw *MWriter) Header() http.Header { |
||||
if mw.ReplaceFunc == nil { |
||||
return mw.writer.Header() |
||||
} |
||||
if mw.cache.header == nil { |
||||
mw.cache.header = mw.writer.Header().Clone() |
||||
} |
||||
return mw.cache.header |
||||
} |
||||
|
||||
func (mw *MWriter) ServeHTTP(w http.ResponseWriter, r *http.Request) { |
||||
mw.writer = w |
||||
mw.request = r |
||||
mw.Next(mw, r) |
||||
if mw.ReplaceFunc != nil { |
||||
mw.ReplaceFunc(mw) |
||||
} |
||||
} |
||||
|
||||
func main() { |
||||
http.ListenAndServe(":8080", NewServer(true)) |
||||
} |
Loading…
Reference in new issue