mirror of https://github.com/hunshcn/gh-proxy
				
				
				
			
						commit
						bdab7ddd67
					
				|  | @ -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
	
	 hunshcn
						hunshcn