diff --git a/main.go b/main.go index 6a4b8db..ca84403 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "encoding/base64" + "fmt" "io" "log" "net/http" @@ -42,6 +43,16 @@ func serve(ctx context.Context, app *app) error { func subShareHandlerApp(app *app) func(w http.ResponseWriter, _ *http.Request) { return func(w http.ResponseWriter, r *http.Request) { + + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE") + w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization,profile-web-page-url,profile-update-interval") + + if r.Method == "OPTIONS" { + w.WriteHeader(http.StatusOK) + return + } + encoder := base64.NewEncoder(base64.StdEncoding, w) defer encoder.Close() @@ -75,6 +86,20 @@ func subShareHandlerApp(app *app) func(w http.ResponseWriter, _ *http.Request) { } } + // handle for client experience + responseOpt, err := app.configure.Get().GetResponseOption(context.Background()) + if err != nil { + app.Logger(context.Background()).Warn("failed to get response option", "error", err) + } + if responseOpt != nil { + if responseOpt.ProfileWebPage != "" { + w.Header().Set("profile-web-page-url", responseOpt.ProfileWebPage) + } + if responseOpt.UpdateIntervalHours > 0 { + w.Header().Set("profile-update-interval", fmt.Sprintf("%d", responseOpt.UpdateIntervalHours)) + } + } + _, err = io.Copy(encoder, buf) if err != nil { app.Logger(context.Background()).Error("failed to copy file to http response", "error", err) diff --git a/sub_configure.go b/sub_configure.go index da8c0d7..818aa96 100644 --- a/sub_configure.go +++ b/sub_configure.go @@ -2,14 +2,20 @@ package main import ( "context" + "errors" "github.com/ServiceWeaver/weaver" ) +var ( + noResponseOptionConfiguredError = errors.New("no response option configured") +) + var _ subConfigureProvider = (*subConfigure)(nil) type subConfigureProvider interface { GetSubFilePaths(ctx context.Context, privateSubToken string) ([]string, error) GetUrlSubs(ctx context.Context, privateSubToken string) ([]string, int, error) + GetResponseOption(ctx context.Context) (*responseOption, error) } type subConfig struct { @@ -20,7 +26,16 @@ type subConfig struct { PublicUrlSubs []string `toml:"public_url_subs"` PrivateUrlSubs []string `toml:"private_url_subs"` - PrivateSubToken string `toml:"private_sub_token"` + PrivateSubToken string `toml:"private_sub_token"` + ResponseOption *responseOption `toml:"response_option,omitempty"` +} + +type responseOption struct { + weaver.AutoMarshal + //return to the client see https://www.clashverge.dev/guide/url_schemes.html#_ + UpdateIntervalHours int `toml:"update_interval_hours,omitempty"` + ProfileWebPage string `toml:"profile_web_page,omitempty"` + // subscription-userinfo: upload=1234; download=2234; total=1024000; expire=2218532293 } type subConfigure struct { @@ -44,3 +59,11 @@ func (s *subConfigure) GetUrlSubs(ctx context.Context, privateSubToken string) ( } return append(config.PrivateUrlSubs, config.PublicUrlSubs...), config.UrlSubFetchTimeoutSeconds, nil } + +func (s *subConfigure) GetResponseOption(ctx context.Context) (*responseOption, error) { + config := s.Config() + if config.ResponseOption == nil { + return nil, noResponseOptionConfiguredError + } + return config.ResponseOption, nil +} diff --git a/weaver.toml b/weaver.toml index ab3cded..619ed8a 100644 --- a/weaver.toml +++ b/weaver.toml @@ -33,8 +33,9 @@ private_url_subs = [ "url4" ] private_sub_token = "gopher" - - +["raycat/subConfigureProvider".response_option] +update_interval_hours = 8 +profile_web_page = "https://x.com" [single] diff --git a/weaver_gen.go b/weaver_gen.go index 8e26451..6936045 100644 --- a/weaver_gen.go +++ b/weaver_gen.go @@ -37,10 +37,10 @@ func init() { Iface: reflect.TypeOf((*subConfigureProvider)(nil)).Elem(), Impl: reflect.TypeOf(subConfigure{}), LocalStubFn: func(impl any, caller string, tracer trace.Tracer) any { - return subConfigureProvider_local_stub{impl: impl.(subConfigureProvider), tracer: tracer, getSubFilePathsMetrics: codegen.MethodMetricsFor(codegen.MethodLabels{Caller: caller, Component: "raycat/subConfigureProvider", Method: "GetSubFilePaths", Remote: false, Generated: true}), getUrlSubsMetrics: codegen.MethodMetricsFor(codegen.MethodLabels{Caller: caller, Component: "raycat/subConfigureProvider", Method: "GetUrlSubs", Remote: false, Generated: true})} + return subConfigureProvider_local_stub{impl: impl.(subConfigureProvider), tracer: tracer, getResponseOptionMetrics: codegen.MethodMetricsFor(codegen.MethodLabels{Caller: caller, Component: "raycat/subConfigureProvider", Method: "GetResponseOption", Remote: false, Generated: true}), getSubFilePathsMetrics: codegen.MethodMetricsFor(codegen.MethodLabels{Caller: caller, Component: "raycat/subConfigureProvider", Method: "GetSubFilePaths", Remote: false, Generated: true}), getUrlSubsMetrics: codegen.MethodMetricsFor(codegen.MethodLabels{Caller: caller, Component: "raycat/subConfigureProvider", Method: "GetUrlSubs", Remote: false, Generated: true})} }, ClientStubFn: func(stub codegen.Stub, caller string) any { - return subConfigureProvider_client_stub{stub: stub, getSubFilePathsMetrics: codegen.MethodMetricsFor(codegen.MethodLabels{Caller: caller, Component: "raycat/subConfigureProvider", Method: "GetSubFilePaths", Remote: true, Generated: true}), getUrlSubsMetrics: codegen.MethodMetricsFor(codegen.MethodLabels{Caller: caller, Component: "raycat/subConfigureProvider", Method: "GetUrlSubs", Remote: true, Generated: true})} + return subConfigureProvider_client_stub{stub: stub, getResponseOptionMetrics: codegen.MethodMetricsFor(codegen.MethodLabels{Caller: caller, Component: "raycat/subConfigureProvider", Method: "GetResponseOption", Remote: true, Generated: true}), getSubFilePathsMetrics: codegen.MethodMetricsFor(codegen.MethodLabels{Caller: caller, Component: "raycat/subConfigureProvider", Method: "GetSubFilePaths", Remote: true, Generated: true}), getUrlSubsMetrics: codegen.MethodMetricsFor(codegen.MethodLabels{Caller: caller, Component: "raycat/subConfigureProvider", Method: "GetUrlSubs", Remote: true, Generated: true})} }, ServerStubFn: func(impl any, addLoad func(uint64, float64)) codegen.Server { return subConfigureProvider_server_stub{impl: impl.(subConfigureProvider), addLoad: addLoad} @@ -111,15 +111,36 @@ type main_local_stub struct { var _ weaver.Main = (*main_local_stub)(nil) type subConfigureProvider_local_stub struct { - impl subConfigureProvider - tracer trace.Tracer - getSubFilePathsMetrics *codegen.MethodMetrics - getUrlSubsMetrics *codegen.MethodMetrics + impl subConfigureProvider + tracer trace.Tracer + getResponseOptionMetrics *codegen.MethodMetrics + getSubFilePathsMetrics *codegen.MethodMetrics + getUrlSubsMetrics *codegen.MethodMetrics } // Check that subConfigureProvider_local_stub implements the subConfigureProvider interface. var _ subConfigureProvider = (*subConfigureProvider_local_stub)(nil) +func (s subConfigureProvider_local_stub) GetResponseOption(ctx context.Context) (r0 *responseOption, err error) { + // Update metrics. + begin := s.getResponseOptionMetrics.Begin() + defer func() { s.getResponseOptionMetrics.End(begin, err != nil, 0, 0) }() + span := trace.SpanFromContext(ctx) + if span.SpanContext().IsValid() { + // Create a child span for this method. + ctx, span = s.tracer.Start(ctx, "main.subConfigureProvider.GetResponseOption", trace.WithSpanKind(trace.SpanKindInternal)) + defer func() { + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + } + span.End() + }() + } + + return s.impl.GetResponseOption(ctx) +} + func (s subConfigureProvider_local_stub) GetSubFilePaths(ctx context.Context, a0 string) (r0 []string, err error) { // Update metrics. begin := s.getSubFilePathsMetrics.Begin() @@ -228,14 +249,62 @@ type main_client_stub struct { var _ weaver.Main = (*main_client_stub)(nil) type subConfigureProvider_client_stub struct { - stub codegen.Stub - getSubFilePathsMetrics *codegen.MethodMetrics - getUrlSubsMetrics *codegen.MethodMetrics + stub codegen.Stub + getResponseOptionMetrics *codegen.MethodMetrics + getSubFilePathsMetrics *codegen.MethodMetrics + getUrlSubsMetrics *codegen.MethodMetrics } // Check that subConfigureProvider_client_stub implements the subConfigureProvider interface. var _ subConfigureProvider = (*subConfigureProvider_client_stub)(nil) +func (s subConfigureProvider_client_stub) GetResponseOption(ctx context.Context) (r0 *responseOption, err error) { + // Update metrics. + var requestBytes, replyBytes int + begin := s.getResponseOptionMetrics.Begin() + defer func() { s.getResponseOptionMetrics.End(begin, err != nil, requestBytes, replyBytes) }() + + span := trace.SpanFromContext(ctx) + if span.SpanContext().IsValid() { + // Create a child span for this method. + ctx, span = s.stub.Tracer().Start(ctx, "main.subConfigureProvider.GetResponseOption", trace.WithSpanKind(trace.SpanKindClient)) + } + + defer func() { + // Catch and return any panics detected during encoding/decoding/rpc. + if err == nil { + err = codegen.CatchPanics(recover()) + if err != nil { + err = errors.Join(weaver.RemoteCallError, err) + } + } + + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + } + span.End() + + }() + + var shardKey uint64 + + // Call the remote method. + var results []byte + results, err = s.stub.Run(ctx, 0, nil, shardKey) + replyBytes = len(results) + if err != nil { + err = errors.Join(weaver.RemoteCallError, err) + return + } + + // Decode the results. + dec := codegen.NewDecoder(results) + r0 = serviceweaver_dec_ptr_responseOption_4a459b91(dec) + err = dec.Error() + return +} + func (s subConfigureProvider_client_stub) GetSubFilePaths(ctx context.Context, a0 string) (r0 []string, err error) { // Update metrics. var requestBytes, replyBytes int @@ -278,7 +347,7 @@ func (s subConfigureProvider_client_stub) GetSubFilePaths(ctx context.Context, a // Call the remote method. requestBytes = len(enc.Data()) var results []byte - results, err = s.stub.Run(ctx, 0, enc.Data(), shardKey) + results, err = s.stub.Run(ctx, 1, enc.Data(), shardKey) replyBytes = len(results) if err != nil { err = errors.Join(weaver.RemoteCallError, err) @@ -334,7 +403,7 @@ func (s subConfigureProvider_client_stub) GetUrlSubs(ctx context.Context, a0 str // Call the remote method. requestBytes = len(enc.Data()) var results []byte - results, err = s.stub.Run(ctx, 1, enc.Data(), shardKey) + results, err = s.stub.Run(ctx, 2, enc.Data(), shardKey) replyBytes = len(results) if err != nil { err = errors.Join(weaver.RemoteCallError, err) @@ -520,6 +589,8 @@ var _ codegen.Server = (*subConfigureProvider_server_stub)(nil) // GetStubFn implements the codegen.Server interface. func (s subConfigureProvider_server_stub) GetStubFn(method string) func(ctx context.Context, args []byte) ([]byte, error) { switch method { + case "GetResponseOption": + return s.getResponseOption case "GetSubFilePaths": return s.getSubFilePaths case "GetUrlSubs": @@ -529,6 +600,26 @@ func (s subConfigureProvider_server_stub) GetStubFn(method string) func(ctx cont } } +func (s subConfigureProvider_server_stub) getResponseOption(ctx context.Context, args []byte) (res []byte, err error) { + // Catch and return any panics detected during encoding/decoding/rpc. + defer func() { + if err == nil { + err = codegen.CatchPanics(recover()) + } + }() + + // TODO(rgrandl): The deferred function above will recover from panics in the + // user code: fix this. + // Call the local method. + r0, appErr := s.impl.GetResponseOption(ctx) + + // Encode the results. + enc := codegen.NewEncoder() + serviceweaver_enc_ptr_responseOption_4a459b91(enc, r0) + enc.Error(appErr) + return enc.Data(), nil +} + func (s subConfigureProvider_server_stub) getSubFilePaths(ctx context.Context, args []byte) (res []byte, err error) { // Catch and return any panics detected during encoding/decoding/rpc. defer func() { @@ -684,6 +775,11 @@ type subConfigureProvider_reflect_stub struct { // Check that subConfigureProvider_reflect_stub implements the subConfigureProvider interface. var _ subConfigureProvider = (*subConfigureProvider_reflect_stub)(nil) +func (s subConfigureProvider_reflect_stub) GetResponseOption(ctx context.Context) (r0 *responseOption, err error) { + err = s.caller("GetResponseOption", ctx, []any{}, []any{&r0}) + return +} + func (s subConfigureProvider_reflect_stub) GetSubFilePaths(ctx context.Context, a0 string) (r0 []string, err error) { err = s.caller("GetSubFilePaths", ctx, []any{a0}, []any{&r0}) return @@ -720,16 +816,43 @@ func (s subURLSourceProvider_reflect_stub) UpdateUrlSub(ctx context.Context, a0 // AutoMarshal implementations. +var _ codegen.AutoMarshal = (*responseOption)(nil) + +type __is_responseOption[T ~struct { + weaver.AutoMarshal + UpdateIntervalHours int "toml:\"update_interval_hours,omitempty\"" + ProfileWebPage string "toml:\"profile_web_page,omitempty\"" +}] struct{} + +var _ __is_responseOption[responseOption] + +func (x *responseOption) WeaverMarshal(enc *codegen.Encoder) { + if x == nil { + panic(fmt.Errorf("responseOption.WeaverMarshal: nil receiver")) + } + enc.Int(x.UpdateIntervalHours) + enc.String(x.ProfileWebPage) +} + +func (x *responseOption) WeaverUnmarshal(dec *codegen.Decoder) { + if x == nil { + panic(fmt.Errorf("responseOption.WeaverUnmarshal: nil receiver")) + } + x.UpdateIntervalHours = dec.Int() + x.ProfileWebPage = dec.String() +} + var _ codegen.AutoMarshal = (*subConfig)(nil) type __is_subConfig[T ~struct { weaver.AutoMarshal - PublicSubFilePaths []string "toml:\"public_sub_file_paths\"" - PrivateSubFilePaths []string "toml:\"private_sub_file_paths\"" - UrlSubFetchTimeoutSeconds int "toml:\"url_sub_fetch_timeout_seconds\"" - PublicUrlSubs []string "toml:\"public_url_subs\"" - PrivateUrlSubs []string "toml:\"private_url_subs\"" - PrivateSubToken string "toml:\"private_sub_token\"" + PublicSubFilePaths []string "toml:\"public_sub_file_paths\"" + PrivateSubFilePaths []string "toml:\"private_sub_file_paths\"" + UrlSubFetchTimeoutSeconds int "toml:\"url_sub_fetch_timeout_seconds\"" + PublicUrlSubs []string "toml:\"public_url_subs\"" + PrivateUrlSubs []string "toml:\"private_url_subs\"" + PrivateSubToken string "toml:\"private_sub_token\"" + ResponseOption *responseOption "toml:\"response_option,omitempty\"" }] struct{} var _ __is_subConfig[subConfig] @@ -744,6 +867,7 @@ func (x *subConfig) WeaverMarshal(enc *codegen.Encoder) { serviceweaver_enc_slice_string_4af10117(enc, x.PublicUrlSubs) serviceweaver_enc_slice_string_4af10117(enc, x.PrivateUrlSubs) enc.String(x.PrivateSubToken) + serviceweaver_enc_ptr_responseOption_4a459b91(enc, x.ResponseOption) } func (x *subConfig) WeaverUnmarshal(dec *codegen.Decoder) { @@ -756,6 +880,7 @@ func (x *subConfig) WeaverUnmarshal(dec *codegen.Decoder) { x.PublicUrlSubs = serviceweaver_dec_slice_string_4af10117(dec) x.PrivateUrlSubs = serviceweaver_dec_slice_string_4af10117(dec) x.PrivateSubToken = dec.String() + x.ResponseOption = serviceweaver_dec_ptr_responseOption_4a459b91(dec) } func serviceweaver_enc_slice_string_4af10117(enc *codegen.Encoder, arg []string) { @@ -781,6 +906,24 @@ func serviceweaver_dec_slice_string_4af10117(dec *codegen.Decoder) []string { return res } +func serviceweaver_enc_ptr_responseOption_4a459b91(enc *codegen.Encoder, arg *responseOption) { + if arg == nil { + enc.Bool(false) + } else { + enc.Bool(true) + (*arg).WeaverMarshal(enc) + } +} + +func serviceweaver_dec_ptr_responseOption_4a459b91(dec *codegen.Decoder) *responseOption { + if !dec.Bool() { + return nil + } + var res responseOption + (&res).WeaverUnmarshal(dec) + return &res +} + // Encoding/decoding implementations. func serviceweaver_enc_slice_byte_87461245(enc *codegen.Encoder, arg []byte) {