
مكتبة وكيل HTTP/HTTPS قابلة للتخصيص للغة Go، تدعم إعادة التوجيه العادية، ونفق CONNECT، واعتراض MITM لاتصالات TLS، والتعديل البرمجي للطلبات والاستجابات.
GoProxy هي مكتبة لإنشاء خادم وكيل HTTP/HTTPS مخصص باستخدام
Go (المعروفة أيضًا باسم Golang)، مع توفر عدة إعدادات قابلة للتهيئة.
الهدف من هذا المشروع هو تقديم خادم وكيل محسّن، قابل للاستخدام مع
كمية معقولة من حركة المرور، لكنه في الوقت نفسه قابل للتخصيص وللبرمجة.
الوكيل نفسه هو ببساطة معالج net/http، لذا يمكنك إضافة عدة
وسطاء (middlewares) فوقه (مثل استعادة panic، والتسجيل logging، والضغط compression، وما إلى ذلك). ويمكن
دمجه بسهولة مع أي مكتبة شبكات HTTP أخرى.
لاستخدام goproxy، يجب على المستخدم ضبط متصفحه (أو أي عميل آخر)
لاستخدام goproxy كوكيل HTTP.
إليك كيفية القيام بذلك في Chrome
وفي Firefox.
إذا قررت البدء بمثال base، فإن عنوان URL الذي يجب استخدامه
كوكيل هو localhost:8080، وهو الافتراضي في مثالنا.
كما يجب عليك الوثوق
بشهادة CA الخاصة بالوكيل، لتجنب أي مشكلة في الشهادات لدى العملاء.
مضيفين محددين، بمقارنة مساواة واحدة أو بتقييم تعبير نمطي (regex)الطلبات والاستجابات قبل إرسالها إلى المتصفحhttp.Transport مخصص لتنفيذ الطلبات إلى الخادم الهدفذاكرة تخزين مؤقت لشهادات MITM، لإعادة استخدامها لاحقًا لطلبات أخرى إلى نفس المضيف، مما يوفر طاقة المعالجة (CPU). غير مفعّلة افتراضيًا، لكن يجب استخدامها في الإنتاج!معالج مخصص، عندما يكون الهدف مسارًا نسبيًا (مثل /ping)Loggerتعطيل تطبيع (canonicalization) ترويسات طلبات HTTP، بتعيين PreventCanonicalization إلى trueهل تستخدم شركتك GoProxy؟ ساعدنا في إبقاء المشروع مُصانًا وبصحة جيدة! دعم GoProxy يتيح لنا تخصيص المزيد من الوقت لإصلاح الأخطاء البرمجية وإضافة ميزات جديدة. في المقابل، إذا اخترت خطة داعم ذهبي (Gold Supporter) أو خطة مؤسسات (Enterprise)، فسنعرض شعار شركتك هنا بكل فخر.
إذا كنت بحاجة إلى دمج GoProxy في مشروعك، أو كنت بحاجة إلى بعض الميزات المخصصة للاحتفاظ بها في نسختك المعدلة (fork)، يمكنك التواصل مع Erik (المشرف الحالي) عبر البريد الإلكتروني، ويمكنكما مناقشة كيف يمكنه مساعدتك كمستشار مستقل مدفوع الأجر.
إذا واجهت أي مشكلة، أو كان لديك اقتراح، أو وجدت خطأ برمجيًا، فلا تتردد في التواصل
عبر فتح issue على GitHub.
هذا مشروع مفتوح المصدر يُدار بواسطة متطوعين، ويسعدنا
مناقشة أي شيء يمكن أن يحسّنه.
تأكد من شرح كل شيء، بما في ذلك السبب وراء المشكلة
وما تريد تغييره، لتسهيل فهم المشكلة.
يمكنك أيضًا فتح Pull Request مباشرة، إذا كان تغييرًا برمجيًا صغيرًا، لكن
يجب أن تشرح كل شيء في الوصف.
إذا فتحت طلب سحب باسم refactoring مع تغيير 5,000 سطر،
فلن ندمجه... :D
كود هذا المشروع مُصدَر تحت رخصة BSD 3-Clause،
مما يجعله مفيدًا للاستخدامات التجارية أيضًا.
إذًا، لقد قمت بإدخال ودمج GoProxy في أحد مشاريعك الشخصية أو في مشروع داخل الشركة التي تعمل بها.
يسعدنا الاطلاع على حلول إبداعية جديدة صُنعت بهذه المكتبة،
فلا تتردد في التواصل مع المشرف المذكور أعلاه عبر البريد الإلكتروني، لشرح
لماذا وجدت هذا المشروع مفيدًا لاحتياجاتك.
إذا كنت قد وقّعت اتفاقية عدم إفصاح (NDA) مع الشركة،
يمكنك أن تقترح عليهم كتابة تدوينة على موقعهم الرسمي حول
هذا الموضوع، بحيث تكون هذه المعلومات علنية باختيارهم، ويمكنك
مشاركة رابط التدوينة معنا :)
الغرض من دراسات الحالة هو مشاركة المجتمع لماذا جميع
المساهمين في هذا المشروع يحسّنون العالم بمساعدتهم وما
الذي يبنيه الناس باستخدامه.
تستخدم قاعدة الكود فحصًا آليًا (lint) على كود طلب السحب (Pull Request) الخاص بك. قبل فتحه، يجب التحقق من أن تغييراتك تحترمه، بتشغيل أداة الفحص على جهازك المحلي، حتى لا تكون هناك أي مفاجآت.
لتثبيت أداة الفحص:
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
سيؤدي هذا إلى إنشاء ملف تنفيذي في مجلد $GOPATH/bin الخاص بك
($GOPATH هو متغير بيئة، عادةً
تكون قيمته مكافئة لـ ~/go، تحقق من قيمته على جهازك إذا
لم تكن متأكدًا منه).
تأكد من تضمين مجلد bin في مسار الصدفة (shell) الخاصة بك، لتتمكن من
استخدام أمر golangci-lint run مباشرة.
للحصول على لمحة عن goproxy، إليك وكيل HTTP/HTTPS أساسي
يقوم فقط بتمرير البيانات إلى الوجهة:
package main
import (
"log"
"net/http"
"github.com/elazarl/goproxy"
)
func main() {
proxy := goproxy.NewProxyHttpServer()
proxy.Verbose = true
log.Fatal(http.ListenAndServe(":8080", proxy))
}
يضيف هذا السطر ترويسة X-GoProxy: yxorPoG-X إلى جميع الطلبات المرسلة عبر الوكيل،
قبل إرسالها إلى الوجهة:
proxy.OnRequest().DoFunc(
func(r *http.Request,ctx *goproxy.ProxyCtx)(*http.Request,*http.Response) {
r.Header.Set("X-GoProxy","yxorPoG-X")
return r,nil
})
عندما يكون مدخل OnRequest() فارغًا، ستقوم الدالة المحددة في DoFunc
بمعالجة جميع الطلبات الواردة إلى الوكيل. في هذه الحالة، ستضيف
ترويسة إلى الطلب وتعيده إلى المتصل.
سيرسل الوكيل الطلب المعدل إلى الوجهة.
يمكنك أيضًا استخدام Do بدلاً من DoFunc، إذا قمت بتنفيذ الواجهة
المحددة في النوع الخاص بك.
⚠️ لاحظ أننا أعدنا قيمة nil كاستجابة. إذا لم تكن الاستجابة المعادة nil، فسيتجاهل goproxy الطلب ويرسل الاستجابة المحددة إلى العميل.
رفض الاتصالات بـ www.reddit.com بين الساعة 8 و17 بالتوقيت المحلي للخادم:
proxy.OnRequest(goproxy.DstHostIs("www.reddit.com")).DoFunc(
func(req *http.Request,ctx *goproxy.ProxyCtx)(*http.Request,*http.Response) {
if h,_,_ := time.Now().Clock(); h >= 8 && h <= 17 {
resp := goproxy.NewResponse(req, goproxy.ContentTypeText, http.StatusForbidden, "Don't waste your time!")
return req, resp
}
return req, nil
})
يعيد DstHostIs ReqCondition، وهي دالة تستقبل *http.Request
وتعيد قيمة منطقية تتحقق مما إذا كان الطلب يفي بالشرط (وسيتم معالجته).
سيعيد DstHostIs("www.reddit.com") ReqCondition تعيد true
عندما يكون الطلب موجهًا إلى "www.reddit.com".
فحص مساواة المضيف غير حساس لحالة الأحرف، ليعكس سلوك
خوادم DNS، لذا حتى إذا كتب المستخدم "www.rEdDit.com"، فستستوفي
المقارنة الشرط.
عندما تكون الساعة بين 8:00 صباحًا و5:59 مساءً، نعيد مباشرة
استجابة في DoFunc()، فلن يستقبل الوجهة البعيدة
الطلب وسيتلقى العميل استجابة "لا تضيّع وقتك!".
import "github.com/elazarl/goproxy"
توجد بعض أمثلة استخدام الوكيل في مجلد examples، والتي
تغطي الحالات الأكثر شيوعًا. ألقِ نظرة عليها وحظًا سعيدًا!
هناك 3 أنواع مختلفة من المعالجات للتحكم في سلوك الوكيل، كما يلي:
// handler called after receiving HTTP CONNECT from the client, and
// before proxy establishes connection with the destination host
httpsHandlers []HttpsHandler
// handler called before proxy sends HTTP request to destination host
reqHandlers []ReqHandler
// handler called after proxy receives HTTP Response from destination host,
// and before proxy forwards the Response to the client
respHandlers []RespHandler
اعتمادًا على ما تريد التحكم فيه، تكون طرق إضافة المعالجات إلى كل قائمة من القوائم السابقة هي:
// Add handlers to httpsHandlers
proxy.OnRequest(some ReqConditions).HandleConnect(YourHandlerFunc())
// Add handlers to reqHandlers
proxy.OnRequest(some ReqConditions).Do(YourReqHandlerFunc())
// Add handlers to respHandlers
proxy.OnResponse(some RespConditions).Do(YourRespHandlerFunc())
مثال:
// This rejects the HTTPS request to *.reddit.com during HTTP CONNECT phase.
// Reddit URL check is case-insensitive because of (?i), so the block will work also if the user types something like rEdDit.com.
proxy.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile("(?i)reddit.*:443$"))).HandleConnect(goproxy.AlwaysReject)
// Be careful about this example! It shows you a common error that you
// need to avoid.
// This will NOT reject the HTTPS request with URL ending with .gif because,
// if the scheme is HTTPS, the proxy will receive only URL.Hostname
// and URL.Port during the HTTP CONNECT phase.
proxy.OnRequest(goproxy.UrlMatches(regexp.MustCompile(`.*gif$`))).HandleConnect(goproxy.AlwaysReject)
// To fix the previous example, here there is the correct way to manipulate
// an HTTP request using URL.Path (target path) as a condition.
proxy.OnRequest(goproxy.UrlMatches(regexp.MustCompile(`.*gif$`))).Do(YourReqHandlerFunc())
إذا حدث خطأ أثناء معالجة طلب عبر الوكيل، فبشكل افتراضي
يعيد الوكيل خطأ HTTP 500 (خطأ داخلي في الخادم) مع رسالة الخطأ كـمحتوى الجسم (body).
إذا كنت تريد تجاوز هذا السلوك، يمكنك تعريف
RespHandler خاص بك يغيّر استجابة الخطأ.
من بين معاملات السياق، يحتوي ctx.Error على الخطأ الذي حدث،
إن وُجد، أو قيمة nil، إذا لم يحدث أي خطأ.
يمكنك التعامل معه كما تشاء، بما في ذلك إرجاع JSON مخصص كجسم. مثال لمعالج خطأ:
proxy.OnResponse().DoFunc(func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response {
var dnsError *net.DNSError
if errors.As(ctx.Error, &dnsError) {
// Do not leak our DNS server's address
dnsError.Server = "<server-redacted>"
return goproxy.NewResponse(ctx.Req, goproxy.ContentTypeText, http.StatusBadGateway, dnsError.Error())
}
return resp
})
إذا حدث خطأ أثناء إرسال البيانات إلى الخادم البعيد الهدف (أو إلى
عميل الوكيل)، يتم استدعاء proxy.ConnectionErrHandler للتعامل مع
الخطأ، إذا كان موجودًا، وإلا سيتم استخدام معالج افتراضي.
يُمرَّر الخطأ كمعامل دالة وليس داخل سياق الوكيل،
لذا لا يتعين عليك التحقق من حقل ctx.Error في هذا المعالج.
في هذا المعالج لديك وصول إلى الاتصال الخام مع عميل
الوكيل (كـio.Writer)، لذا يمكنك إرسال أي بيانات HTTP عبره،
إذا لزم الأمر، تحتوي على بيانات الخطأ.
لا يوجد ضمان بأن الاتصال لم يُغلق بالفعل، لذا
قد يعيد Write() خطأ.
سيتم إغلاق الاتصال تلقائيًا بواسطة مكتبة الوكيل بعد
استدعاء معالج الخطأ، لذا لا داعي للقلق بشأنه.
تم إنشاء هذا المشروع منذ 10 سنوات، وقد وصل إلى مرحلة
النضج. يمكن استخدامه بأمان في الإنتاج، والعديد من المشاريع
تفعل ذلك بالفعل.
إذا حدث أي تغيير جذري في المستقبل، فسيتم إصدار نسخة جديدة من
وحدة Go النمطية (مثل v2).