feat: add rfc8908 basic api endpoint implementation
This commit is contained in:
72
rfc8908/handler.go
Normal file
72
rfc8908/handler.go
Normal file
@ -0,0 +1,72 @@
|
||||
package rfc8908
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type IsCaptiveFunc func(*http.Request) (bool, error)
|
||||
|
||||
// RFC8098
|
||||
// See https://tools.ietf.org/html/rfc8908 - "5. API State Structure"
|
||||
type Response struct {
|
||||
Captive bool `json:"captive"`
|
||||
UserPortalURL string `json:"user-portal-url,omitempty"`
|
||||
VenueInfoURL string `json:"venue-info-url,omitempty"`
|
||||
CanExtendSession *bool `json:"can-extend-session,omitempty"`
|
||||
SecondsRemaining *int `json:"seconds-remaining,omitempty"`
|
||||
BytesRemaining *int `json:"bytes-remaining,omitempty"`
|
||||
}
|
||||
|
||||
// Handler returns a basic implementation of a Captive Portal API endpoint
|
||||
// as described in the IETF 8908 RFC (https://tools.ietf.org/html/rfc8908)
|
||||
func Handler(isCaptive IsCaptiveFunc, opts ...OptionsFunc) http.HandlerFunc {
|
||||
options := DefaultOptions()
|
||||
|
||||
for _, o := range opts {
|
||||
o(options)
|
||||
}
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
isCaptive, err := isCaptive(r)
|
||||
if err != nil {
|
||||
panic(errors.Wrap(err, "could not retrieve client id"))
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
var bytesRemaining *int
|
||||
if options.BytesRemaining != nil {
|
||||
bytesRemaining, err = options.BytesRemaining(ctx)
|
||||
if err != nil {
|
||||
panic(errors.Wrap(err, "could not retrieve client remaining bytes"))
|
||||
}
|
||||
}
|
||||
|
||||
var secondsRemaining *int
|
||||
if options.SecondsRemaining != nil {
|
||||
secondsRemaining, err = options.SecondsRemaining(ctx)
|
||||
if err != nil {
|
||||
panic(errors.Wrap(err, "could not retrieve client remaining seconds"))
|
||||
}
|
||||
}
|
||||
|
||||
w.Header().Add("Content-Type", "application/captive+json")
|
||||
|
||||
encoder := json.NewEncoder(w)
|
||||
res := &Response{
|
||||
Captive: isCaptive,
|
||||
VenueInfoURL: options.VenueInfoURL,
|
||||
UserPortalURL: options.UserPortalURL,
|
||||
CanExtendSession: options.CanExtendSession,
|
||||
SecondsRemaining: secondsRemaining,
|
||||
BytesRemaining: bytesRemaining,
|
||||
}
|
||||
|
||||
if err := encoder.Encode(res); err != nil {
|
||||
panic(errors.WithStack(err))
|
||||
}
|
||||
}
|
||||
}
|
51
rfc8908/option.go
Normal file
51
rfc8908/option.go
Normal file
@ -0,0 +1,51 @@
|
||||
package rfc8908
|
||||
|
||||
import "context"
|
||||
|
||||
type Options struct {
|
||||
UserPortalURL string
|
||||
VenueInfoURL string
|
||||
CanExtendSession *bool
|
||||
SecondsRemaining SecondsRemainingFunc
|
||||
BytesRemaining BytesRemainingFunc
|
||||
}
|
||||
|
||||
type OptionsFunc func(*Options)
|
||||
|
||||
type SecondsRemainingFunc func(context.Context) (*int, error)
|
||||
|
||||
type BytesRemainingFunc func(context.Context) (*int, error)
|
||||
|
||||
func DefaultOptions() *Options {
|
||||
return &Options{}
|
||||
}
|
||||
|
||||
func WithUserPortalURL(userPortalURL string) OptionsFunc {
|
||||
return func(o *Options) {
|
||||
o.UserPortalURL = userPortalURL
|
||||
}
|
||||
}
|
||||
|
||||
func WithVenueInfoURL(venueInfoURL string) OptionsFunc {
|
||||
return func(o *Options) {
|
||||
o.VenueInfoURL = venueInfoURL
|
||||
}
|
||||
}
|
||||
|
||||
func WithCanExtendSession(canExtend bool) OptionsFunc {
|
||||
return func(o *Options) {
|
||||
o.CanExtendSession = &canExtend
|
||||
}
|
||||
}
|
||||
|
||||
func WithSecondsRemainingFunc(fn SecondsRemainingFunc) OptionsFunc {
|
||||
return func(o *Options) {
|
||||
o.SecondsRemaining = fn
|
||||
}
|
||||
}
|
||||
|
||||
func WithBytesRemainingFunc(fn BytesRemainingFunc) OptionsFunc {
|
||||
return func(o *Options) {
|
||||
o.BytesRemaining = fn
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user