ملاحظة: تم تحديث Gongular مؤخرًا ، وإذا كنت تبحث عن الإصدار السابق ، فسيتم وضع علامة عليه على أنه v.1.0
Gongular هو إطار خادم HTTP لتطوير واجهات برمجة التطبيقات بسهولة. إنه مثل Gin Gonic ، ولكنه يتميز بحقن التبعية يشبه الزاوي (أو الربيع) وتعامل إدخال أفضل. في معظم الأوقات ، يجب تحويل إدخال المستخدم إلى بيانات منظمة ثم يجب التحقق من صحتها. يستغرق الأمر وقتًا طويلاً وهو عمل متكرر ، يهدف Gongular إلى تقليل هذا التعقيد من خلال توفير رسم خرائط للطلب مع التحقق من العلامات.
ملحوظة: Gongular هو إطار رأي ويعتمد بشكل كبير على الانعكاس لتحقيق هذه الوظائف. على الرغم من وجود اختبارات لضمان عملها بلا عيب ، إلا أنني منفتح على المساهمات والآراء حول كيفية تحسينها.
يهدف Gongular إلى أن يكون بسيطًا قدر الإمكان مع توفير المرونة. المثال أدناه يكفي للرد على المستخدم مع IP الخاص به.
type WelcomeMessage struct {}
func ( w * WelcomeMessage ) Handle ( c * gongular. Context ) error {
c . SetBody ( c . Request (). RemoteAddr )
}
g := gongular . NewEngine ()
g . GET ( "/" , & WelcomeMessage {})
g . ListenAndServe ( ":8000" ) جميع معالجات HTTP في Gongular هي هياكل مع وظيفة Handle(c *gongular.Context) error أو واجهة أخرى عبر RequestHandler ، التي تم تنفيذها. كائنات معالج الطلب مرنة. يمكن أن يكون لديهم حقول مختلفة ، حيث بعض الحقول ذات الأسماء المحددة خاصة. على سبيل المثال ، إذا كنت ترغب في ربط معلمات المسار ، فيجب أن يحتوي كائن المعالج الخاص بك على حقل يدعى Param وهو بنية مسطحة. كما يمكنك الحصول على حقل Query يقوم أيضًا بتعيين معلمات الاستعلام. يتيح لك حقل Body خريطة لـ JSON Body ، ويتيح لك Form Filement ربط التقديمات بالملفات.
type MyHandler struct {
Param struct {
UserID int
}
Query struct {
Name string
Age int
Level float64
}
Body struct {
Comment string
Choices [] string
Address struct {
City string
Country string
Hello string
}
}
}
func ( m * MyHandler ) Handle ( c * gongular. Context ) error {
c . SetBody ( "Wow so much params" )
return nil
}نحن نستخدم Julienschmidt/httprouter لتلبية الطلبات والقيام بربط حدودي للطلبات. لذا فإن التنسيق: variablename ، *يتم دعم Somepath في المسارات. لاحظ أنه يمكنك استخدام علامة بنية صالحة للتحقق من صحة المعلمات.
type PathParamHandler struct {
Param struct {
Username string
}
}
func ( p * PathParamHandler ) Handle ( c * Context ) error {
c . SetBody ( p . Param . Username )
return nil
} تشبه معلمة الاستعلام إلى حد كبير معلمات المسار ، والفرق الوحيد الذي يجب أن يكون اسم الحقل هو Query ويجب أن يكون أيضًا بنية مسطحة بدون معلمات أو صفائف داخلية. معاملات الاستعلام حساسة للحالة واستخدم الاسم الدقيق لخاصية البنية افتراضيًا. يمكنك استخدام علامة q struct لتحديد مفتاح المعلمة
type QueryParamHandler struct {
Query struct {
Username string `q:"username"`
Age int
}
}
func ( p * QueryParamHandler ) Handle ( c * Context ) error {
println ( p . Param . Age )
c . SetBody ( p . Param . Username )
return nil
}يمكن تحليل أجسام طلب JSON على غرار معلمات الاستعلام ، ولكن يمكن أن يكون جسم JSON هيكلًا تعسفيًا.
type BodyParamHandler struct {
Body struct {
Username string
Age int
Preferences [] string
Comments [] struct {
OwnerID int
Message string
}
}
}
func ( p * BodyParamHandler ) Handle ( c * Context ) error {
println ( p . Body . Age )
c . SetBody ( p . Body . Preferences + len ( c . Body . Comments ))
return nil
} يرجى ملاحظة أن Body Form لا يمكن أن يكونا حاضرين في نفس المعالج ، لأن Gongular سوف يخلط بين ما يجب فعله مع هيئة الطلب.
type formHandler struct {
Form struct {
Age int
Name string
Favorite string
Fraction float64
}
}
func ( q * formHandler ) Handle ( c * Context ) error {
c . SetBody ( fmt . Sprintf ( "%d:%s:%s:%.2f" ,
q . Form . Age , q . Form . Name , q . Form . Favorite , q . Form . Fraction ))
return nil
}
e . GetRouter (). POST ( "/submit" , & formHandler {}) بالنسبة للملفات التي تم تحميلها ، نستخدم هيكلًا خاصًا للاحتفاظ بها في قيمة النموذج لهيكل الطلب. يحمل UploadedFile multipart.File و multipart.Header ، يمكنك فعل أي شيء تريده.
type UploadedFile struct {
File multipart. File
Header * multipart. FileHeader
}يمكنك استخدامه في المعالج مثل ما يلي:
type formUploadTest struct {
Form struct {
SomeFile * UploadedFile
RegularValue int
}
}
func ( f * formUploadTest ) Handle ( c * Context ) error {
s := sha256 . New ()
io . Copy ( s , f . Form . SomeFile . File )
resp := fmt . Sprintf ( "%x:%d" , s . Sum ( nil ), f . Form . RegularValue )
c . SetBody ( resp )
return nil
}
e . GetRouter (). POST ( "/upload" , & formUploadTest {})يمكن أن يكون للمسارات معالجات متعددة ، تسمى الوسيطة ، والتي قد تكون مفيدة في تجميع الطلبات والقيام بعمل أولي قبل بعض الطرق. على سبيل المثال ، التجميع والتوجيه التاليان صالحان:
type simpleHandler struct {}
func ( s * simpleHandler ) Handle ( c * Context ) error {
c . SetBody ( "hi" )
return nil
}
// The middle ware that will fail if you supply 5 as a user ID
type middlewareFailIfUserId5 struct {
Param struct {
UserID int
}
}
func ( m * middlewareFailIfUserId5 ) Handle ( c * Context ) error {
if m . Param . UserID == 5 {
c . Status ( http . StatusTeapot )
c . SetBody ( "Sorry" )
c . StopChain ()
}
return nil
}
r := e . GetRouter ()
g := r . Group ( "/api/user/:UserID" , & middlewareFailIfUserId5 {})
g . GET ( "/name" , & simpleHandler {})
g . GET ( "/wow" , & simpleHandler {})
/*
The example responses:
/api/user/5/name -> Sorry
/api/user/4/name -> hi
/api/user/1/wow -> hi
*/ نستخدم Asaskevich/Govalidator كإطار التحقق من الصحة. إذا لم يمر المدخلات المقدمة خطوة التحقق من الصحة ، فسيتم إرجاع http.statusbadrequest (400) المستخدم بالسبب. يمكن استخدام التحقق من الصحة في الاستعلام أو المهرج أو الجسم أو النموذج. يمكن رؤية مثال على النحو التالي:
type QueryParamHandler struct {
Query struct {
Username string `valid:"alpha"`
Age int
}
}
func ( p * QueryParamHandler ) Handle ( c * Context ) error {
println ( p . Param . Age )
c . SetBody ( p . Param . Username )
return nil
} إذا تم تعيين طلب مع حقل اسم مستخدم غير صالح ، فإنه يعيد ParseError .
أحد الأشياء التي تجعل من الأطر الأخرى هو أنه يوفر حقنًا آمنًا للقيمة للمعالجات. يمكن استخدامه لتخزين اتصالات قاعدة البيانات ، أو بعض الأدوات الخارجية الأخرى التي تريد أن تكون قابلة للتطبيق في معالجك ، ولكن لا تريد أن تجعلها عالمية ، أو فقط الحصول عليها من وظيفة عالمية أخرى قد تلوث المساحة. يتم توفير التبعيات الموردة كما هو-هي معالجات الطريق وهي خاصة لجهاز التوجيه الموردين ، لا يوجد شيء عالمي.
يسمح Gongular بحقن أساسي للغاية: يمكنك تقديم قيمة لـ Gongular.engine ، وتوفر لك معالجك إذا كنت تريدها في وظيفة معالجك. لا يشبه Guice أو Spring مثل الحقن ، فهو لا يحل تبعيات الحقن ، فهو يوفر القيمة فقط ، بحيث لا تستخدم القيم العالمية ، ويجعل الاختبار أسهل ، حيث يمكنك فقط اختبار وظيفة المعالج عن طريق استياء الواجهات التي تحبها.
type myHandler struct {
Param struct {
UserID uint
}
Database * sql. DB
}
func ( i * myHandler ) Handle ( c * Context ) error {
c . SetBody ( fmt . Sprintf ( "%p:%d" , i . Database , i . Param . UserID ))
return nil
}
db := new (sql. DB )
e . Provide ( db )
e . GetRouter (). GET ( "/my/db/interaction/:UserID" , & myHandler {})يعمل الحقن الأساسي بشكل رائع ، ولكن إذا كنت ترغب في توفير نفس النوع من القيمة أكثر من مرة ، فيجب عليك استخدام الحقن الرئيسي بحيث يمكن أن يختلف التأمر.
type injectKey struct {
Val1 int `inject:"val1"`
Val2 int `inject:"val2"`
}
func ( i * injectKey ) Handle ( c * Context ) error {
c . SetBody ( i . Val1 * i . Val2 )
return nil
}
e . ProvideWithKey ( "val1" , 71 )
e . ProvideWithKey ( "val2" , 97 )
e . GetRouter (). GET ( "/" , & injectKey {}) في بعض الأحيان ، قد لا يكون توفير القيم كما هو كافٍ لك. يمكنك اختيار ping قاعدة البيانات ، وإنشاء معاملة ، والحصول على قيمة من تجمع ، وهذا يتطلب تنفيذ منطق مخصص. يتيح لك Gongular كتابة وظيفة CustomProvideFunction التي تتيح لك توفير القيمة المفضلة لديك مع أي منطق تريده.
type injectCustom struct {
DB * sql. DB
}
func ( i * injectCustom ) Handle ( c * Context ) error {
c . SetBody ( fmt . Sprintf ( "%p" , i . DB ))
return nil
}
e := newEngineTest ()
var d * sql. DB
e . CustomProvide ( & sql. DB {}, func ( c * Context ) ( interface {}, error ) {
d = new (sql. DB )
return d , nil
})
e . GetRouter (). GET ( "/" , & injectCustom {}) تتيح لك وظائف Provide الافتراضي حقن التطبيقات فقط. حقن الواجهات لن يعمل. أثناء الحقن ، سوف يبحث الحاقن عن نوع متوفر وفشل. على سبيل المثال ، لن يعمل الرمز التالي:
type injectKey struct {
DB MySQLInterface `inject:"db"`
}
func ( i * injectKey ) Handle ( c * Context ) error {
c . SetBody ( "yay" )
return nil
}
e . ProvideWithKey ( "db" , & sql. DB {})
e . GetRouter (). GET ( "/" , & injectKey {}) هذا سوف يسبب خطأ الحاقن. إذا كنت ترغب في حقن واجهات ، فيجب عليك استخدام ProvideUnsafe . ProvideUnsafe هو حقن مفتاح/قيمة صارم. لا يمكنك توفير قيم متعددة لنفس المفتاح.
مثال الاستخدام:
type injectKey struct {
DB MySQLInterface `inject:"db"`
}
func ( i * injectKey ) Handle ( c * Context ) error {
c . SetBody ( "yay" )
return nil
}
e . ProvideUnsafe ( "db" , initializeDB ())
// This would cause a panic
// e.ProvideUnsafe("db", &sql.DB{})
e . GetRouter (). GET ( "/" , & injectKey {})context.SetBody(interface{}) : تعيين هيئة الاستجابة ليتم تسلسلها.context.Status(int) : يحدد حالة الاستجابة إن لم يكن تم تعيينه سابقًاcontext.MustStatus(int) : يتجاوز الحالة المكتوبة مسبقًاcontext.Request() : إرجاع طلب HTTP الخام الأساسيcontext.Header(string,string) : تعيين رأس استجابة معين.context.Finalize() : تستخدم لكتابة الاستجابة للعميل ، وعادة ما لا ينبغي استخدامها بخلاف بانيشاندلر لأن Gongular يعتني بالاستجابة.context.Logger() : إرجاع مسجل السياق. يتيح لك رد الاتصال على الطريق ، الذي تم تعيينه على مستوى العالم للمحرك ، الحصول على الإحصائيات للطلب المكتمل. أنه يحتوي على معلومات شائعة ، بما في ذلك سجلات الطلبات والمعالجات المتطابقة ، وكم الوقت الذي استغرقته في كل معالج ، والوقت الإجمالي ، وحجم الاستجابة الإجمالي المكتوب ورمز الحالة النهائي ، والذي يمكن أن يكون مفيدًا لك لإرساله إلى خدمة مراقبة أخرى ، أو مجرد بعض Elasticsearch لتحليل السجل.
type RouteStat struct {
Request * http. Request
Handlers [] HandlerStat
MatchedPath string
TotalDuration time. Duration
ResponseSize int
ResponseCode int
Logs * bytes. Buffer
} في حالة إرجاع خطأ من وظيفتك ، أو يحدث خطأ آخر مما يجعل الطلب غير مرضٍ ، يستدعي gongular.Engine وظيفة معالج الخطأ ، والتي تتخلف عن المعالج التالي:
var defaultErrorHandler = func ( err error , c * Context ) {
c . logger . Println ( "An error has occurred:" , err )
switch err := err .( type ) {
case InjectionError :
c . MustStatus ( http . StatusInternalServerError )
c . logger . Println ( "Could not inject the requested field" , err )
case ValidationError :
c . MustStatus ( http . StatusBadRequest )
c . SetBody ( map [ string ] interface {}{ "ValidationError" : err })
case ParseError :
c . MustStatus ( http . StatusBadRequest )
c . SetBody ( map [ string ] interface {}{ "ParseError" : err })
default :
c . SetBody ( err . Error ())
c . MustStatus ( http . StatusInternalServerError )
}
c . StopChain ()
} يدعم Gongular اتصالات WebSocket أيضًا. تشبه وظيفة المعالج واجهة معالج الطريق العادية ، ولكنها تسمح أيضًا بإنهاء الاتصال إذا كنت ترغب في معالجه Before .
type WebsocketHandler interface {
Before ( c * Context ) (http. Header , error )
Handle ( conn * websocket. Conn )
}بادئ ذي بدء ، لا تُرجع وظيفة المقبض خطأً ، لأنها تنفيذ مستمر. المستخدم مسؤول عن جميع تفاعل WebSocket. ثانياً ، قبل أن يتم تطبيق مرشح قبل ترقية الطلب إلى WebSocket. قد يكون من المفيد تصفية الطلب وإرجاع خطأ لن يفتح Websocket ولكن يغلقه بخطأ. HTTP.Header هو للرد مع http.header الذي يتيح إعداد ملف تعريف الارتباط. يمكن حذفه إن لم يكن المرغوب فيه.
إن الشيء الجميل في WebSockethandler هو أنه يدعم طلبات Param والاستعلام أيضًا ، بحيث يمكن إجراء كل الربط والتحقق من الصحة قبل الطلب ، ويمكنك استخدامه في معالجك.
type wsTest struct {
Param struct {
UserID int
}
Query struct {
Track bool
Username string
}
}
func ( w * wsTest ) Before ( c * Context ) (http. Header , error ) {
return nil , nil
}
func ( w * wsTest ) Handle ( conn * websocket. Conn ) {
_ , msg , err := conn . ReadMessage ()
if err != nil {
conn . Close ()
}
toSend := fmt . Sprintf ( "%s:%d:%s:%t" , msg , w . Param . UserID , w . Query . Username , w . Query . Track )
conn . WriteMessage ( websocket . TextMessage , [] byte ( toSend ))
conn . Close ()
}