// Unwraps a rpcError and returns the correct error type. func unwrapError(rpcError *rpcError) error { if rpcError != nil { switch rpcError.Type { case "InternalServerError": return core.InternalServerError(rpcError.Value) case "NotSupportedError": return core.NotSupportedError(rpcError.Value) case "MalformedRequestError": return core.MalformedRequestError(rpcError.Value) case "UnauthorizedError": return core.UnauthorizedError(rpcError.Value) case "NotFoundError": return core.NotFoundError(rpcError.Value) case "SyntaxError": return core.SyntaxError(rpcError.Value) case "SignatureValidationError": return core.SignatureValidationError(rpcError.Value) case "CertificateIssuanceError": return core.CertificateIssuanceError(rpcError.Value) case "NoSuchRegistrationError": return core.NoSuchRegistrationError(rpcError.Value) case "TooManyRPCRequestsError": return core.TooManyRPCRequestsError(rpcError.Value) case "RateLimitedError": return core.RateLimitedError(rpcError.Value) case "ServiceUnavailableError": return core.ServiceUnavailableError(rpcError.Value) default: return errors.New(rpcError.Value) } } return nil }
// Unwraps a rpcError and returns the correct error type. func unwrapError(rpcError rpcError) (err error) { if rpcError.Value != "" { switch rpcError.Type { case "InternalServerError": err = core.InternalServerError(rpcError.Value) case "NotSupportedError": err = core.NotSupportedError(rpcError.Value) case "MalformedRequestError": err = core.MalformedRequestError(rpcError.Value) case "UnauthorizedError": err = core.UnauthorizedError(rpcError.Value) case "NotFoundError": err = core.NotFoundError(rpcError.Value) case "SyntaxError": err = core.SyntaxError(rpcError.Value) case "SignatureValidationError": err = core.SignatureValidationError(rpcError.Value) case "CertificateIssuanceError": err = core.CertificateIssuanceError(rpcError.Value) case "NoSuchRegistrationError": err = core.NoSuchRegistrationError(rpcError.Value) case "TooManyRPCRequestsError": err = core.TooManyRPCRequestsError(rpcError.Value) case "RateLimitedError": err = core.RateLimitedError(rpcError.Value) default: err = errors.New(rpcError.Value) } } return }
// Unwraps a rpcError and returns the correct error type. func unwrapError(rpcError *rpcError) error { if rpcError != nil { switch rpcError.Type { case "InternalServerError": return core.InternalServerError(rpcError.Value) case "NotSupportedError": return core.NotSupportedError(rpcError.Value) case "MalformedRequestError": return core.MalformedRequestError(rpcError.Value) case "UnauthorizedError": return core.UnauthorizedError(rpcError.Value) case "NotFoundError": return core.NotFoundError(rpcError.Value) case "SignatureValidationError": return core.SignatureValidationError(rpcError.Value) case "NoSuchRegistrationError": return core.NoSuchRegistrationError(rpcError.Value) case "TooManyRPCRequestsError": return core.TooManyRPCRequestsError(rpcError.Value) case "RateLimitedError": return core.RateLimitedError(rpcError.Value) default: if strings.HasPrefix(rpcError.Type, "urn:") { return &probs.ProblemDetails{ Type: probs.ProblemType(rpcError.Type), Detail: rpcError.Value, HTTPStatus: rpcError.HTTPStatus, } } return errors.New(rpcError.Value) } } return nil }
// Start starts the AMQP-RPC server and handles reconnections, this will block // until a fatal error is returned or AmqpRPCServer.Stop() is called and all // remaining messages are processed. func (rpc *AmqpRPCServer) Start(c *cmd.AMQPConfig) error { tooManyGoroutines := rpcResponse{ Error: wrapError(core.TooManyRPCRequestsError("RPC server has spawned too many Goroutines")), } tooManyRequestsResponse, err := json.Marshal(tooManyGoroutines) if err != nil { return err } rpc.tooManyRequestsResponse = tooManyRequestsResponse err = rpc.connection.connect(c) if err != nil { return err } rpc.mu.Lock() rpc.connected = true rpc.mu.Unlock() go rpc.catchSignals() for { select { case msg, ok := <-rpc.connection.messages(): if ok { rpc.stats.TimingDuration(fmt.Sprintf("RPC.MessageLag.%s", rpc.serverQueue), rpc.clk.Now().Sub(msg.Timestamp), 1.0) if rpc.maxConcurrentRPCServerRequests > 0 && atomic.LoadInt64(&rpc.currentGoroutines) >= rpc.maxConcurrentRPCServerRequests { rpc.replyTooManyRequests(msg) rpc.stats.Inc(fmt.Sprintf("RPC.CallsDropped.%s", rpc.serverQueue), 1, 1.0) break // this breaks the select, not the for } rpc.stats.Inc(fmt.Sprintf("RPC.Traffic.Rx.%s", rpc.serverQueue), int64(len(msg.Body)), 1.0) go func() { atomic.AddInt64(&rpc.currentGoroutines, 1) defer atomic.AddInt64(&rpc.currentGoroutines, -1) startedProcessing := rpc.clk.Now() if rpc.handleDelivery != nil { rpc.handleDelivery(msg) } else { rpc.processMessage(msg) } rpc.stats.TimingDuration(fmt.Sprintf("RPC.ServerProcessingLatency.%s", msg.Type), time.Since(startedProcessing), 1.0) }() } else { rpc.mu.RLock() if rpc.done { // chan has been closed by rpc.connection.Cancel rpc.log.Info(" [!] Finished processing messages") rpc.mu.RUnlock() return nil } rpc.mu.RUnlock() rpc.log.Info(" [!] Got channel close, but no signal to shut down. Continuing.") } case err = <-rpc.connection.closeChannel(): rpc.log.Info(fmt.Sprintf(" [!] Server channel closed: %s", rpc.serverQueue)) rpc.connection.reconnect(c, rpc.log) } } }
func TestWrapError(t *testing.T) { testCases := []error{ core.InternalServerError("foo"), core.NotSupportedError("foo"), core.MalformedRequestError("foo"), core.UnauthorizedError("foo"), core.NotFoundError("foo"), core.SignatureValidationError("foo"), core.CertificateIssuanceError("foo"), core.NoSuchRegistrationError("foo"), core.RateLimitedError("foo"), core.TooManyRPCRequestsError("foo"), errors.New("foo"), } for _, c := range testCases { wrapped := wrapError(c) test.AssertEquals(t, wrapped.Type, reflect.TypeOf(c).Name()) test.AssertEquals(t, wrapped.Value, "foo") unwrapped := unwrapError(wrapped) test.AssertEquals(t, wrapped.Type, reflect.TypeOf(unwrapped).Name()) test.AssertEquals(t, unwrapped.Error(), "foo") } complicated := []struct { given error expected error }{ { &probs.ProblemDetails{ Type: probs.ConnectionProblem, Detail: "whoops", HTTPStatus: 417, }, &probs.ProblemDetails{ Type: probs.ConnectionProblem, Detail: "whoops", HTTPStatus: 417, }, }, { &probs.ProblemDetails{Type: "invalid", Detail: "hm"}, errors.New("hm"), }, { errors.New(""), errors.New(""), }, } for i, tc := range complicated { actual := unwrapError(wrapError(tc.given)) if !reflect.DeepEqual(tc.expected, actual) { t.Errorf("rpc error wrapping case %d: want %#v, got %#v", i, tc.expected, actual) } } }
// Start starts the AMQP-RPC server and handles reconnections, this will block // until a fatal error is returned or AmqpRPCServer.Stop() is called and all // remaining messages are processed. func (rpc *AmqpRPCServer) Start(c cmd.Config) error { tooManyGoroutines := rpcResponse{ Error: wrapError(core.TooManyRPCRequestsError("RPC server has spawned too many Goroutines")), } tooManyRequestsResponse, err := json.Marshal(tooManyGoroutines) if err != nil { return err } rpc.tooManyRequestsResponse = tooManyRequestsResponse err = rpc.connection.connect(c) if err != nil { return err } rpc.mu.Lock() rpc.connected = true rpc.mu.Unlock() go rpc.catchSignals() for { select { case msg, ok := <-rpc.connection.messages(): if ok { if rpc.maxConcurrentRPCServerRequests > 0 && atomic.LoadInt64(&rpc.currentGoroutines) >= rpc.maxConcurrentRPCServerRequests { rpc.replyTooManyRequests(msg) break // this breaks the select, not the for } go func() { atomic.AddInt64(&rpc.currentGoroutines, 1) defer atomic.AddInt64(&rpc.currentGoroutines, -1) rpc.processMessage(msg) }() } else { rpc.mu.RLock() if rpc.done { // chan has been closed by rpc.connection.Cancel rpc.log.Info(" [!] Finished processing messages") rpc.mu.RUnlock() return nil } rpc.mu.RUnlock() rpc.log.Info(" [!] Got channel close, but no signal to shut down. Continuing.") } case err = <-rpc.connection.closeChannel(): rpc.log.Info(fmt.Sprintf(" [!] Server channel closed: %s", rpc.serverQueue)) rpc.connection.reconnect(c, rpc.log) } } }
func TestWrapError(t *testing.T) { testCases := []error{ core.InternalServerError("foo"), core.NotSupportedError("foo"), core.MalformedRequestError("foo"), core.UnauthorizedError("foo"), core.NotFoundError("foo"), core.SyntaxError("foo"), core.SignatureValidationError("foo"), core.CertificateIssuanceError("foo"), core.NoSuchRegistrationError("foo"), core.RateLimitedError("foo"), core.TooManyRPCRequestsError("foo"), } for _, c := range testCases { wrapped := wrapError(c) test.AssertEquals(t, wrapped.Type, reflect.TypeOf(c).Name()) test.AssertEquals(t, wrapped.Value, "foo") unwrapped := unwrapError(wrapped) test.AssertEquals(t, wrapped.Type, reflect.TypeOf(unwrapped).Name()) test.AssertEquals(t, unwrapped.Error(), "foo") } }
// Start starts the AMQP-RPC server and handles reconnections, this will block // until a fatal error is returned or AmqpRPCServer.Stop() is called and all // remaining messages are processed. func (rpc *AmqpRPCServer) Start(c cmd.Config) error { tooManyGoroutines := rpcResponse{ Error: wrapError(core.TooManyRPCRequestsError("RPC server has spawned too many Goroutines")), } tooManyRequestsResponse, err := json.Marshal(tooManyGoroutines) if err != nil { return err } rpc.tooManyRequestsResponse = tooManyRequestsResponse go rpc.catchSignals() for { rpc.dMu.Lock() if rpc.done { rpc.dMu.Unlock() break } rpc.dMu.Unlock() var err error rpc.Channel, err = AmqpChannel(c) if err != nil { return err } rpc.connectionHandler(rpc) msgs, err := amqpSubscribe(rpc.Channel, rpc.serverQueue, rpc.consumerName, rpc.log) if err != nil { return err } rpc.connected = true rpc.log.Info(" [!] Connected to AMQP") closeChan := rpc.Channel.NotifyClose(make(chan *amqp.Error, 1)) for blocking := true; blocking; { select { case msg, ok := <-msgs: if ok { if rpc.maxConcurrentRPCServerRequests > 0 && atomic.LoadInt64(&rpc.currentGoroutines) >= rpc.maxConcurrentRPCServerRequests { rpc.replyTooManyRequests(msg) break // this breaks the select, not the for } go func() { atomic.AddInt64(&rpc.currentGoroutines, 1) defer atomic.AddInt64(&rpc.currentGoroutines, -1) rpc.processMessage(msg) }() } else { // chan has been closed by rpc.channel.Cancel rpc.log.Info(" [!] Finished processing messages") return nil } case err = <-closeChan: rpc.connected = false rpc.log.Warning(fmt.Sprintf(" [!] AMQP Channel closed, will reconnect in 5 seconds: [%s]", err)) time.Sleep(time.Second * 5) blocking = false } } } return nil }