Status error errors message could not find attached object code 0

Contribute to kubernetes/apimachinery development by creating an account on GitHub.
/* Copyright 2014 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the «License»); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an «AS IS» BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package errors import ( «encoding/json» «errors» «fmt» «net/http» «reflect» «strings» metav1 «k8s.io/apimachinery/pkg/apis/meta/v1» «k8s.io/apimachinery/pkg/runtime» «k8s.io/apimachinery/pkg/runtime/schema» «k8s.io/apimachinery/pkg/util/validation/field» ) // StatusError is an error intended for consumption by a REST API server; it can also be // reconstructed by clients from a REST response. Public to allow easy type switches. type StatusError struct { ErrStatus metav1.Status } // APIStatus is exposed by errors that can be converted to an api.Status object // for finer grained details. type APIStatus interface { Status() metav1.Status } var _ error = &StatusError{} var knownReasons = map[metav1.StatusReason]struct{}{ // metav1.StatusReasonUnknown : {} metav1.StatusReasonUnauthorized: {}, metav1.StatusReasonForbidden: {}, metav1.StatusReasonNotFound: {}, metav1.StatusReasonAlreadyExists: {}, metav1.StatusReasonConflict: {}, metav1.StatusReasonGone: {}, metav1.StatusReasonInvalid: {}, metav1.StatusReasonServerTimeout: {}, metav1.StatusReasonTimeout: {}, metav1.StatusReasonTooManyRequests: {}, metav1.StatusReasonBadRequest: {}, metav1.StatusReasonMethodNotAllowed: {}, metav1.StatusReasonNotAcceptable: {}, metav1.StatusReasonRequestEntityTooLarge: {}, metav1.StatusReasonUnsupportedMediaType: {}, metav1.StatusReasonInternalError: {}, metav1.StatusReasonExpired: {}, metav1.StatusReasonServiceUnavailable: {}, } // Error implements the Error interface. func (e *StatusError) Error() string { return e.ErrStatus.Message } // Status allows access to e’s status without having to know the detailed workings // of StatusError. func (e *StatusError) Status() metav1.Status { return e.ErrStatus } // DebugError reports extended info about the error to debug output. func (e *StatusError) DebugError() (string, []interface{}) { if out, err := json.MarshalIndent(e.ErrStatus, «», » «); err == nil { return «server response object: %s», []interface{}{string(out)} } return «server response object: %#v», []interface{}{e.ErrStatus} } // HasStatusCause returns true if the provided error has a details cause // with the provided type name. // It supports wrapped errors and returns false when the error is nil. func HasStatusCause(err error, name metav1.CauseType) bool { _, ok := StatusCause(err, name) return ok } // StatusCause returns the named cause from the provided error if it exists and // the error unwraps to the type APIStatus. Otherwise it returns false. func StatusCause(err error, name metav1.CauseType) (metav1.StatusCause, bool) { status, ok := err.(APIStatus) if (ok || errors.As(err, &status)) && status.Status().Details != nil { for _, cause := range status.Status().Details.Causes { if cause.Type == name { return cause, true } } } return metav1.StatusCause{}, false } // UnexpectedObjectError can be returned by FromObject if it’s passed a non-status object. type UnexpectedObjectError struct { Object runtime.Object } // Error returns an error message describing ‘u’. func (u *UnexpectedObjectError) Error() string { return fmt.Sprintf(«unexpected object: %v», u.Object) } // FromObject generates an StatusError from an metav1.Status, if that is the type of obj; otherwise, // returns an UnexpecteObjectError. func FromObject(obj runtime.Object) error { switch t := obj.(type) { case *metav1.Status: return &StatusError{ErrStatus: *t} case runtime.Unstructured: var status metav1.Status obj := t.UnstructuredContent() if !reflect.DeepEqual(obj[«kind»], «Status») { break } if err := runtime.DefaultUnstructuredConverter.FromUnstructured(t.UnstructuredContent(), &status); err != nil { return err } if status.APIVersion != «v1» && status.APIVersion != «meta.k8s.io/v1» { break } return &StatusError{ErrStatus: status} } return &UnexpectedObjectError{obj} } // NewNotFound returns a new error which indicates that the resource of the kind and the name was not found. func NewNotFound(qualifiedResource schema.GroupResource, name string) *StatusError { return &StatusError{metav1.Status{ Status: metav1.StatusFailure, Code: http.StatusNotFound, Reason: metav1.StatusReasonNotFound, Details: &metav1.StatusDetails{ Group: qualifiedResource.Group, Kind: qualifiedResource.Resource, Name: name, }, Message: fmt.Sprintf(«%s %q not found», qualifiedResource.String(), name), }} } // NewAlreadyExists returns an error indicating the item requested exists by that identifier. func NewAlreadyExists(qualifiedResource schema.GroupResource, name string) *StatusError { return &StatusError{metav1.Status{ Status: metav1.StatusFailure, Code: http.StatusConflict, Reason: metav1.StatusReasonAlreadyExists, Details: &metav1.StatusDetails{ Group: qualifiedResource.Group, Kind: qualifiedResource.Resource, Name: name, }, Message: fmt.Sprintf(«%s %q already exists», qualifiedResource.String(), name), }} } // NewGenerateNameConflict returns an error indicating the server // was not able to generate a valid name for a resource. func NewGenerateNameConflict(qualifiedResource schema.GroupResource, name string, retryAfterSeconds int) *StatusError { return &StatusError{metav1.Status{ Status: metav1.StatusFailure, Code: http.StatusConflict, Reason: metav1.StatusReasonAlreadyExists, Details: &metav1.StatusDetails{ Group: qualifiedResource.Group, Kind: qualifiedResource.Resource, Name: name, RetryAfterSeconds: int32(retryAfterSeconds), }, Message: fmt.Sprintf( «%s %q already exists, the server was not able to generate a unique name for the object», qualifiedResource.String(), name), }} } // NewUnauthorized returns an error indicating the client is not authorized to perform the requested // action. func NewUnauthorized(reason string) *StatusError { message := reason if len(message) == 0 { message = «not authorized» } return &StatusError{metav1.Status{ Status: metav1.StatusFailure, Code: http.StatusUnauthorized, Reason: metav1.StatusReasonUnauthorized, Message: message, }} } // NewForbidden returns an error indicating the requested action was forbidden func NewForbidden(qualifiedResource schema.GroupResource, name string, err error) *StatusError { var message string if qualifiedResource.Empty() { message = fmt.Sprintf(«forbidden: %v», err) } else if name == «» { message = fmt.Sprintf(«%s is forbidden: %v», qualifiedResource.String(), err) } else { message = fmt.Sprintf(«%s %q is forbidden: %v», qualifiedResource.String(), name, err) } return &StatusError{metav1.Status{ Status: metav1.StatusFailure, Code: http.StatusForbidden, Reason: metav1.StatusReasonForbidden, Details: &metav1.StatusDetails{ Group: qualifiedResource.Group, Kind: qualifiedResource.Resource, Name: name, }, Message: message, }} } // NewConflict returns an error indicating the item can’t be updated as provided. func NewConflict(qualifiedResource schema.GroupResource, name string, err error) *StatusError { return &StatusError{metav1.Status{ Status: metav1.StatusFailure, Code: http.StatusConflict, Reason: metav1.StatusReasonConflict, Details: &metav1.StatusDetails{ Group: qualifiedResource.Group, Kind: qualifiedResource.Resource, Name: name, }, Message: fmt.Sprintf(«Operation cannot be fulfilled on %s %q: %v», qualifiedResource.String(), name, err), }} } // NewApplyConflict returns an error including details on the requests apply conflicts func NewApplyConflict(causes []metav1.StatusCause, message string) *StatusError { return &StatusError{ErrStatus: metav1.Status{ Status: metav1.StatusFailure, Code: http.StatusConflict, Reason: metav1.StatusReasonConflict, Details: &metav1.StatusDetails{ // TODO: Get obj details here? Causes: causes, }, Message: message, }} } // NewGone returns an error indicating the item no longer available at the server and no forwarding address is known. // DEPRECATED: Please use NewResourceExpired instead. func NewGone(message string) *StatusError { return &StatusError{metav1.Status{ Status: metav1.StatusFailure, Code: http.StatusGone, Reason: metav1.StatusReasonGone, Message: message, }} } // NewResourceExpired creates an error that indicates that the requested resource content has expired from // the server (usually due to a resourceVersion that is too old). func NewResourceExpired(message string) *StatusError { return &StatusError{metav1.Status{ Status: metav1.StatusFailure, Code: http.StatusGone, Reason: metav1.StatusReasonExpired, Message: message, }} } // NewInvalid returns an error indicating the item is invalid and cannot be processed. func NewInvalid(qualifiedKind schema.GroupKind, name string, errs field.ErrorList) *StatusError { causes := make([]metav1.StatusCause, 0, len(errs)) for i := range errs { err := errs[i] causes = append(causes, metav1.StatusCause{ Type: metav1.CauseType(err.Type), Message: err.ErrorBody(), Field: err.Field, }) } err := &StatusError{metav1.Status{ Status: metav1.StatusFailure, Code: http.StatusUnprocessableEntity, Reason: metav1.StatusReasonInvalid, Details: &metav1.StatusDetails{ Group: qualifiedKind.Group, Kind: qualifiedKind.Kind, Name: name, Causes: causes, }, }} aggregatedErrs := errs.ToAggregate() if aggregatedErrs == nil { err.ErrStatus.Message = fmt.Sprintf(«%s %q is invalid», qualifiedKind.String(), name) } else { err.ErrStatus.Message = fmt.Sprintf(«%s %q is invalid: %v», qualifiedKind.String(), name, aggregatedErrs) } return err } // NewBadRequest creates an error that indicates that the request is invalid and can not be processed. func NewBadRequest(reason string) *StatusError { return &StatusError{metav1.Status{ Status: metav1.StatusFailure, Code: http.StatusBadRequest, Reason: metav1.StatusReasonBadRequest, Message: reason, }} } // NewTooManyRequests creates an error that indicates that the client must try again later because // the specified endpoint is not accepting requests. More specific details should be provided // if client should know why the failure was limited. func NewTooManyRequests(message string, retryAfterSeconds int) *StatusError { return &StatusError{metav1.Status{ Status: metav1.StatusFailure, Code: http.StatusTooManyRequests, Reason: metav1.StatusReasonTooManyRequests, Message: message, Details: &metav1.StatusDetails{ RetryAfterSeconds: int32(retryAfterSeconds), }, }} } // NewServiceUnavailable creates an error that indicates that the requested service is unavailable. func NewServiceUnavailable(reason string) *StatusError { return &StatusError{metav1.Status{ Status: metav1.StatusFailure, Code: http.StatusServiceUnavailable, Reason: metav1.StatusReasonServiceUnavailable, Message: reason, }} } // NewMethodNotSupported returns an error indicating the requested action is not supported on this kind. func NewMethodNotSupported(qualifiedResource schema.GroupResource, action string) *StatusError { return &StatusError{metav1.Status{ Status: metav1.StatusFailure, Code: http.StatusMethodNotAllowed, Reason: metav1.StatusReasonMethodNotAllowed, Details: &metav1.StatusDetails{ Group: qualifiedResource.Group, Kind: qualifiedResource.Resource, }, Message: fmt.Sprintf(«%s is not supported on resources of kind %q», action, qualifiedResource.String()), }} } // NewServerTimeout returns an error indicating the requested action could not be completed due to a // transient error, and the client should try again. func NewServerTimeout(qualifiedResource schema.GroupResource, operation string, retryAfterSeconds int) *StatusError { return &StatusError{metav1.Status{ Status: metav1.StatusFailure, Code: http.StatusInternalServerError, Reason: metav1.StatusReasonServerTimeout, Details: &metav1.StatusDetails{ Group: qualifiedResource.Group, Kind: qualifiedResource.Resource, Name: operation, RetryAfterSeconds: int32(retryAfterSeconds), }, Message: fmt.Sprintf(«The %s operation against %s could not be completed at this time, please try again.», operation, qualifiedResource.String()), }} } // NewServerTimeoutForKind should not exist. Server timeouts happen when accessing resources, the Kind is just what we // happened to be looking at when the request failed. This delegates to keep code sane, but we should work towards removing this. func NewServerTimeoutForKind(qualifiedKind schema.GroupKind, operation string, retryAfterSeconds int) *StatusError { return NewServerTimeout(schema.GroupResource{Group: qualifiedKind.Group, Resource: qualifiedKind.Kind}, operation, retryAfterSeconds) } // NewInternalError returns an error indicating the item is invalid and cannot be processed. func NewInternalError(err error) *StatusError { return &StatusError{metav1.Status{ Status: metav1.StatusFailure, Code: http.StatusInternalServerError, Reason: metav1.StatusReasonInternalError, Details: &metav1.StatusDetails{ Causes: []metav1.StatusCause{{Message: err.Error()}}, }, Message: fmt.Sprintf(«Internal error occurred: %v», err), }} } // NewTimeoutError returns an error indicating that a timeout occurred before the request // could be completed. Clients may retry, but the operation may still complete. func NewTimeoutError(message string, retryAfterSeconds int) *StatusError { return &StatusError{metav1.Status{ Status: metav1.StatusFailure, Code: http.StatusGatewayTimeout, Reason: metav1.StatusReasonTimeout, Message: fmt.Sprintf(«Timeout: %s», message), Details: &metav1.StatusDetails{ RetryAfterSeconds: int32(retryAfterSeconds), }, }} } // NewTooManyRequestsError returns an error indicating that the request was rejected because // the server has received too many requests. Client should wait and retry. But if the request // is perishable, then the client should not retry the request. func NewTooManyRequestsError(message string) *StatusError { return &StatusError{metav1.Status{ Status: metav1.StatusFailure, Code: http.StatusTooManyRequests, Reason: metav1.StatusReasonTooManyRequests, Message: fmt.Sprintf(«Too many requests: %s», message), }} } // NewRequestEntityTooLargeError returns an error indicating that the request // entity was too large. func NewRequestEntityTooLargeError(message string) *StatusError { return &StatusError{metav1.Status{ Status: metav1.StatusFailure, Code: http.StatusRequestEntityTooLarge, Reason: metav1.StatusReasonRequestEntityTooLarge, Message: fmt.Sprintf(«Request entity too large: %s», message), }} } // NewGenericServerResponse returns a new error for server responses that are not in a recognizable form. func NewGenericServerResponse(code int, verb string, qualifiedResource schema.GroupResource, name, serverMessage string, retryAfterSeconds int, isUnexpectedResponse bool) *StatusError { reason := metav1.StatusReasonUnknown message := fmt.Sprintf(«the server responded with the status code %d but did not return more information», code) switch code { case http.StatusConflict: if verb == «POST» { reason = metav1.StatusReasonAlreadyExists } else { reason = metav1.StatusReasonConflict } message = «the server reported a conflict» case http.StatusNotFound: reason = metav1.StatusReasonNotFound message = «the server could not find the requested resource» case http.StatusBadRequest: reason = metav1.StatusReasonBadRequest message = «the server rejected our request for an unknown reason» case http.StatusUnauthorized: reason = metav1.StatusReasonUnauthorized message = «the server has asked for the client to provide credentials» case http.StatusForbidden: reason = metav1.StatusReasonForbidden // the server message has details about who is trying to perform what action. Keep its message. message = serverMessage case http.StatusNotAcceptable: reason = metav1.StatusReasonNotAcceptable // the server message has details about what types are acceptable if len(serverMessage) == 0 || serverMessage == «unknown» { message = «the server was unable to respond with a content type that the client supports» } else { message = serverMessage } case http.StatusUnsupportedMediaType: reason = metav1.StatusReasonUnsupportedMediaType // the server message has details about what types are acceptable message = serverMessage case http.StatusMethodNotAllowed: reason = metav1.StatusReasonMethodNotAllowed message = «the server does not allow this method on the requested resource» case http.StatusUnprocessableEntity: reason = metav1.StatusReasonInvalid message = «the server rejected our request due to an error in our request» case http.StatusServiceUnavailable: reason = metav1.StatusReasonServiceUnavailable message = «the server is currently unable to handle the request» case http.StatusGatewayTimeout: reason = metav1.StatusReasonTimeout message = «the server was unable to return a response in the time allotted, but may still be processing the request» case http.StatusTooManyRequests: reason = metav1.StatusReasonTooManyRequests message = «the server has received too many requests and has asked us to try again later» default: if code >= 500 { reason = metav1.StatusReasonInternalError message = fmt.Sprintf(«an error on the server (%q) has prevented the request from succeeding», serverMessage) } } switch { case !qualifiedResource.Empty() && len(name) > 0: message = fmt.Sprintf(«%s (%s %s %s)», message, strings.ToLower(verb), qualifiedResource.String(), name) case !qualifiedResource.Empty(): message = fmt.Sprintf(«%s (%s %s)», message, strings.ToLower(verb), qualifiedResource.String()) } var causes []metav1.StatusCause if isUnexpectedResponse { causes = []metav1.StatusCause{ { Type: metav1.CauseTypeUnexpectedServerResponse, Message: serverMessage, }, } } else { causes = nil } return &StatusError{metav1.Status{ Status: metav1.StatusFailure, Code: int32(code), Reason: reason, Details: &metav1.StatusDetails{ Group: qualifiedResource.Group, Kind: qualifiedResource.Resource, Name: name, Causes: causes, RetryAfterSeconds: int32(retryAfterSeconds), }, Message: message, }} } // IsNotFound returns true if the specified error was created by NewNotFound. // It supports wrapped errors and returns false when the error is nil. func IsNotFound(err error) bool { reason, code := reasonAndCodeForError(err) if reason == metav1.StatusReasonNotFound { return true } if _, ok := knownReasons[reason]; !ok && code == http.StatusNotFound { return true } return false } // IsAlreadyExists determines if the err is an error which indicates that a specified resource already exists. // It supports wrapped errors and returns false when the error is nil. func IsAlreadyExists(err error) bool { return ReasonForError(err) == metav1.StatusReasonAlreadyExists } // IsConflict determines if the err is an error which indicates the provided update conflicts. // It supports wrapped errors and returns false when the error is nil. func IsConflict(err error) bool { reason, code := reasonAndCodeForError(err) if reason == metav1.StatusReasonConflict { return true } if _, ok := knownReasons[reason]; !ok && code == http.StatusConflict { return true } return false } // IsInvalid determines if the err is an error which indicates the provided resource is not valid. // It supports wrapped errors and returns false when the error is nil. func IsInvalid(err error) bool { reason, code := reasonAndCodeForError(err) if reason == metav1.StatusReasonInvalid { return true } if _, ok := knownReasons[reason]; !ok && code == http.StatusUnprocessableEntity { return true } return false } // IsGone is true if the error indicates the requested resource is no longer available. // It supports wrapped errors and returns false when the error is nil. func IsGone(err error) bool { reason, code := reasonAndCodeForError(err) if reason == metav1.StatusReasonGone { return true } if _, ok := knownReasons[reason]; !ok && code == http.StatusGone { return true } return false } // IsResourceExpired is true if the error indicates the resource has expired and the current action is // no longer possible. // It supports wrapped errors and returns false when the error is nil. func IsResourceExpired(err error) bool { return ReasonForError(err) == metav1.StatusReasonExpired } // IsNotAcceptable determines if err is an error which indicates that the request failed due to an invalid Accept header // It supports wrapped errors and returns false when the error is nil. func IsNotAcceptable(err error) bool { reason, code := reasonAndCodeForError(err) if reason == metav1.StatusReasonNotAcceptable { return true } if _, ok := knownReasons[reason]; !ok && code == http.StatusNotAcceptable { return true } return false } // IsUnsupportedMediaType determines if err is an error which indicates that the request failed due to an invalid Content-Type header // It supports wrapped errors and returns false when the error is nil. func IsUnsupportedMediaType(err error) bool { reason, code := reasonAndCodeForError(err) if reason == metav1.StatusReasonUnsupportedMediaType { return true } if _, ok := knownReasons[reason]; !ok && code == http.StatusUnsupportedMediaType { return true } return false } // IsMethodNotSupported determines if the err is an error which indicates the provided action could not // be performed because it is not supported by the server. // It supports wrapped errors and returns false when the error is nil. func IsMethodNotSupported(err error) bool { reason, code := reasonAndCodeForError(err) if reason == metav1.StatusReasonMethodNotAllowed { return true } if _, ok := knownReasons[reason]; !ok && code == http.StatusMethodNotAllowed { return true } return false } // IsServiceUnavailable is true if the error indicates the underlying service is no longer available. // It supports wrapped errors and returns false when the error is nil. func IsServiceUnavailable(err error) bool { reason, code := reasonAndCodeForError(err) if reason == metav1.StatusReasonServiceUnavailable { return true } if _, ok := knownReasons[reason]; !ok && code == http.StatusServiceUnavailable { return true } return false } // IsBadRequest determines if err is an error which indicates that the request is invalid. // It supports wrapped errors and returns false when the error is nil. func IsBadRequest(err error) bool { reason, code := reasonAndCodeForError(err) if reason == metav1.StatusReasonBadRequest { return true } if _, ok := knownReasons[reason]; !ok && code == http.StatusBadRequest { return true } return false } // IsUnauthorized determines if err is an error which indicates that the request is unauthorized and // requires authentication by the user. // It supports wrapped errors and returns false when the error is nil. func IsUnauthorized(err error) bool { reason, code := reasonAndCodeForError(err) if reason == metav1.StatusReasonUnauthorized { return true } if _, ok := knownReasons[reason]; !ok && code == http.StatusUnauthorized { return true } return false } // IsForbidden determines if err is an error which indicates that the request is forbidden and cannot // be completed as requested. // It supports wrapped errors and returns false when the error is nil. func IsForbidden(err error) bool { reason, code := reasonAndCodeForError(err) if reason == metav1.StatusReasonForbidden { return true } if _, ok := knownReasons[reason]; !ok && code == http.StatusForbidden { return true } return false } // IsTimeout determines if err is an error which indicates that request times out due to long // processing. // It supports wrapped errors and returns false when the error is nil. func IsTimeout(err error) bool { reason, code := reasonAndCodeForError(err) if reason == metav1.StatusReasonTimeout { return true } if _, ok := knownReasons[reason]; !ok && code == http.StatusGatewayTimeout { return true } return false } // IsServerTimeout determines if err is an error which indicates that the request needs to be retried // by the client. // It supports wrapped errors and returns false when the error is nil. func IsServerTimeout(err error) bool { // do not check the status code, because no https status code exists that can // be scoped to retryable timeouts. return ReasonForError(err) == metav1.StatusReasonServerTimeout } // IsInternalError determines if err is an error which indicates an internal server error. // It supports wrapped errors and returns false when the error is nil. func IsInternalError(err error) bool { reason, code := reasonAndCodeForError(err) if reason == metav1.StatusReasonInternalError { return true } if _, ok := knownReasons[reason]; !ok && code == http.StatusInternalServerError { return true } return false } // IsTooManyRequests determines if err is an error which indicates that there are too many requests // that the server cannot handle. // It supports wrapped errors and returns false when the error is nil. func IsTooManyRequests(err error) bool { reason, code := reasonAndCodeForError(err) if reason == metav1.StatusReasonTooManyRequests { return true } // IsTooManyRequests’ checking of code predates the checking of the code in // the other Is* functions. In order to maintain backward compatibility, this // does not check that the reason is unknown. if code == http.StatusTooManyRequests { return true } return false } // IsRequestEntityTooLargeError determines if err is an error which indicates // the request entity is too large. // It supports wrapped errors and returns false when the error is nil. func IsRequestEntityTooLargeError(err error) bool { reason, code := reasonAndCodeForError(err) if reason == metav1.StatusReasonRequestEntityTooLarge { return true } // IsRequestEntityTooLargeError’s checking of code predates the checking of // the code in the other Is* functions. In order to maintain backward // compatibility, this does not check that the reason is unknown. if code == http.StatusRequestEntityTooLarge { return true } return false } // IsUnexpectedServerError returns true if the server response was not in the expected API format, // and may be the result of another HTTP actor. // It supports wrapped errors and returns false when the error is nil. func IsUnexpectedServerError(err error) bool { status, ok := err.(APIStatus) if (ok || errors.As(err, &status)) && status.Status().Details != nil { for _, cause := range status.Status().Details.Causes { if cause.Type == metav1.CauseTypeUnexpectedServerResponse { return true } } } return false } // IsUnexpectedObjectError determines if err is due to an unexpected object from the master. // It supports wrapped errors and returns false when the error is nil. func IsUnexpectedObjectError(err error) bool { uoe, ok := err.(*UnexpectedObjectError) return err != nil && (ok || errors.As(err, &uoe)) } // SuggestsClientDelay returns true if this error suggests a client delay as well as the // suggested seconds to wait, or false if the error does not imply a wait. It does not // address whether the error *should* be retried, since some errors (like a 3xx) may // request delay without retry. // It supports wrapped errors and returns false when the error is nil. func SuggestsClientDelay(err error) (int, bool) { t, ok := err.(APIStatus) if (ok || errors.As(err, &t)) && t.Status().Details != nil { switch t.Status().Reason { // this StatusReason explicitly requests the caller to delay the action case metav1.StatusReasonServerTimeout: return int(t.Status().Details.RetryAfterSeconds), true } // If the client requests that we retry after a certain number of seconds if t.Status().Details.RetryAfterSeconds > 0 { return int(t.Status().Details.RetryAfterSeconds), true } } return 0, false } // ReasonForError returns the HTTP status for a particular error. // It supports wrapped errors and returns StatusReasonUnknown when // the error is nil or doesn’t have a status. func ReasonForError(err error) metav1.StatusReason { if status, ok := err.(APIStatus); ok || errors.As(err, &status) { return status.Status().Reason } return metav1.StatusReasonUnknown } func reasonAndCodeForError(err error) (metav1.StatusReason, int32) { if status, ok := err.(APIStatus); ok || errors.As(err, &status) { return status.Status().Reason, status.Status().Code } return metav1.StatusReasonUnknown, 0 } // ErrorReporter converts generic errors into runtime.Object errors without // requiring the caller to take a dependency on meta/v1 (where Status lives). // This prevents circular dependencies in core watch code. type ErrorReporter struct { code int verb string reason string } // NewClientErrorReporter will respond with valid v1.Status objects that report // unexpected server responses. Primarily used by watch to report errors when // we attempt to decode a response from the server and it is not in the form // we expect. Because watch is a dependency of the core api, we can’t return // meta/v1.Status in that package and so much inject this interface to convert a // generic error as appropriate. The reason is passed as a unique status cause // on the returned status, otherwise the generic «ClientError» is returned. func NewClientErrorReporter(code int, verb string, reason string) *ErrorReporter { return &ErrorReporter{ code: code, verb: verb, reason: reason, } } // AsObject returns a valid error runtime.Object (a v1.Status) for the given // error, using the code and verb of the reporter type. The error is set to // indicate that this was an unexpected server response. func (r *ErrorReporter) AsObject(err error) runtime.Object { status := NewGenericServerResponse(r.code, r.verb, schema.GroupResource{}, «», err.Error(), 0, true) if status.ErrStatus.Details == nil { status.ErrStatus.Details = &metav1.StatusDetails{} } reason := r.reason if len(reason) == 0 { reason = «ClientError» } status.ErrStatus.Details.Causes = append(status.ErrStatus.Details.Causes, metav1.StatusCause{ Type: metav1.CauseType(reason), Message: err.Error(), }) return &status.ErrStatus }

Ошибки — это хорошо. Автор материала, перевод которого мы сегодня публикуем, говорит, что уверен в том, что эта идея известна всем. На первый взгляд ошибки кажутся чем-то страшным. Им могут сопутствовать какие-то потери. Ошибка, сделанная на публике, вредит авторитету того, кто её совершил. Но, совершая ошибки, мы на них учимся, а значит, попадая в следующий раз в ситуацию, в которой раньше вели себя неправильно, делаем всё как нужно.

Выше мы говорили об ошибках, которые люди совершают в обычной жизни. Ошибки в программировании — это нечто иное. Сообщения об ошибках помогают нам улучшать код, они позволяют сообщать пользователям наших проектов о том, что что-то пошло не так, и, возможно, рассказывают пользователям о том, как нужно вести себя для того, чтобы ошибок больше не возникало.

Этот материал, посвящённый обработке ошибок в JavaScript, разбит на три части. Сначала мы сделаем общий обзор системы обработки ошибок в JavaScript и поговорим об объектах ошибок. После этого мы поищем ответ на вопрос о том, что делать с ошибками, возникающими в серверном коде (в частности, при использовании связки Node.js + Express.js). Далее — обсудим обработку ошибок в React.js. Фреймворки, которые будут здесь рассматриваться, выбраны по причине их огромной популярности. Однако рассматриваемые здесь принципы работы с ошибками универсальны, поэтому вы, даже если не пользуетесь Express и React, без труда сможете применить то, что узнали, к тем инструментам, с которыми работаете.

Код демонстрационного проекта, используемого в данном материале, можно найти в этом репозитории.

1. Ошибки в JavaScript и универсальные способы работы с ними

Если в вашем коде что-то пошло не так, вы можете воспользоваться следующей конструкцией.

throw new Error('something went wrong')

В ходе выполнения этой команды будет создан экземпляр объекта Error и будет сгенерировано (или, как говорят, «выброшено») исключение с этим объектом. Инструкция throw может генерировать исключения, содержащие произвольные выражения. При этом выполнение скрипта остановится в том случае, если не были предприняты меры по обработке ошибки.

Начинающие JS-программисты обычно не используют инструкцию throw. Они, как правило, сталкиваются с исключениями, выдаваемыми либо средой выполнения языка, либо сторонними библиотеками. Когда это происходит — в консоль попадает нечто вроде ReferenceError: fs is not defined и выполнение программы останавливается.

▍Объект Error

У экземпляров объекта Error есть несколько свойств, которыми мы можем пользоваться. Первое интересующее нас свойство — message. Именно сюда попадает та строка, которую можно передать конструктору ошибки в качестве аргумента. Например, ниже показано создание экземпляра объекта Error и вывод в консоль переданной конструктором строки через обращение к его свойству message.

const myError = new Error('please improve your code')
console.log(myError.message) // please improve your code

Второе свойство объекта, очень важное, представляет собой трассировку стека ошибки. Это — свойство stack. Обратившись к нему можно просмотреть стек вызовов (историю ошибки), который показывает последовательность операций, приведшую к неправильной работе программы. В частности, это позволяет понять — в каком именно файле содержится сбойный код, и увидеть, какая последовательность вызовов функций привела к ошибке. Вот пример того, что можно увидеть, обратившись к свойству stack.

Error: please improve your code
 at Object.<anonymous> (/Users/gisderdube/Documents/_projects/hacking.nosync/error-handling/src/general.js:1:79)
 at Module._compile (internal/modules/cjs/loader.js:689:30)
 at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10)
 at Module.load (internal/modules/cjs/loader.js:599:32)
 at tryModuleLoad (internal/modules/cjs/loader.js:538:12)
 at Function.Module._load (internal/modules/cjs/loader.js:530:3)
 at Function.Module.runMain (internal/modules/cjs/loader.js:742:12)
 at startup (internal/bootstrap/node.js:266:19)
 at bootstrapNodeJSCore (internal/bootstrap/node.js:596:3)

Здесь, в верхней части, находится сообщение об ошибке, затем следует указание на тот участок кода, выполнение которого вызвало ошибку, потом описывается то место, откуда был вызван этот сбойный участок. Это продолжается до самого «дальнего» по отношению к ошибке фрагмента кода.

▍Генерирование и обработка ошибок

Создание экземпляра объекта Error, то есть, выполнение команды вида new Error(), ни к каким особым последствиям не приводит. Интересные вещи начинают происходить после применения оператора throw, который генерирует ошибку. Как уже было сказано, если такую ошибку не обработать, выполнение скрипта остановится. При этом нет никакой разницы — был ли оператор throw использован самим программистом, произошла ли ошибка в некоей библиотеке или в среде выполнения языка (в браузере или в Node.js). Поговорим о различных сценариях обработки ошибок.

▍Конструкция try…catch

Блок try...catch представляет собой самый простой способ обработки ошибок, о котором часто забывают. В наши дни, правда, он используется гораздо интенсивнее чем раньше, благодаря тому, что его можно применять для обработки ошибок в конструкциях async/await.

Этот блок можно использовать для обработки любых ошибок, происходящих в синхронном коде. Рассмотрим пример.

const a = 5

try {
    console.log(b) // переменная b не объявлена - возникает ошибка
} catch (err) {
    console.error(err) // в консоль попадает сообщение об ошибке и стек ошибки
}

console.log(a) // выполнение скрипта не останавливается, данная команда выполняется

Если бы в этом примере мы не заключили бы сбойную команду console.log(b) в блок try...catch, то выполнение скрипта было бы остановлено.

▍Блок finally

Иногда случается так, что некий код нужно выполнить независимо от того, произошла ошибка или нет. Для этого можно, в конструкции try...catch, использовать третий, необязательный, блок — finally. Часто его использование эквивалентно некоему коду, который идёт сразу после try...catch, но в некоторых ситуациях он может пригодиться. Вот пример его использования.

const a = 5

try {
    console.log(b) // переменная b не объявлена - возникает ошибка
} catch (err) {
    console.error(err) // в консоль попадает сообщение об ошибке и стек ошибки
} finally {
    console.log(a) // этот код будет выполнен в любом случае
}

▍Асинхронные механизмы — коллбэки

Программируя на JavaScript всегда стоит обращать внимание на участки кода, выполняющиеся асинхронно. Если у вас имеется асинхронная функция и в ней возникает ошибка, скрипт продолжит выполняться. Когда асинхронные механизмы в JS реализуются с использованием коллбэков (кстати, делать так не рекомендуется), соответствующий коллбэк (функция обратного вызова) обычно получает два параметра. Это нечто вроде параметра err, который может содержать ошибку, и result — с результатами выполнения асинхронной операции. Выглядит это примерно так:

myAsyncFunc(someInput, (err, result) => {
    if(err) return console.error(err) // порядок работы с объектом ошибки мы рассмотрим позже
    console.log(result)
})

Если в коллбэк попадает ошибка, она видна там в виде параметра err. В противном случае в этот параметр попадёт значение undefined или null. Если оказалось, что в err что-то есть, важно отреагировать на это, либо так как в нашем примере, воспользовавшись командой return, либо воспользовавшись конструкцией if...else и поместив в блок else команды для работы с результатом выполнения асинхронной операции. Речь идёт о том, чтобы, в том случае, если произошла ошибка, исключить возможность работы с результатом, параметром result, который в таком случае может иметь значение undefined. Работа с таким значением, если предполагается, например, что оно содержит объект, сама может вызвать ошибку. Скажем, это произойдёт при попытке использовать конструкцию result.data или подобную ей.

▍Асинхронные механизмы — промисы

Для выполнения асинхронных операций в JavaScript лучше использовать не коллбэки а промисы. Тут, в дополнение к улучшенной читабельности кода, имеются и более совершенные механизмы обработки ошибок. А именно, возиться с объектом ошибки, который может попасть в функцию обратного вызова, при использовании промисов не нужно. Здесь для этой цели предусмотрен специальный блок catch. Он перехватывает все ошибки, произошедшие в промисах, которые находятся до него, или все ошибки, которые произошли в коде после предыдущего блока catch. Обратите внимание на то, что если в промисе произошла ошибка, для обработки которой нет блока catch, это не остановит выполнение скрипта, но сообщение об ошибке будет не особенно удобочитаемым.

(node:7741) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: something went wrong
(node:7741) DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code. */

В результате можно порекомендовать всегда, при работе с промисами, использовать блок catch. Взглянем на пример.

Promise.resolve(1)
    .then(res => {
        console.log(res) // 1

        throw new Error('something went wrong')

        return Promise.resolve(2)
    })
    .then(res => {
        console.log(res) // этот блок выполнен не будет
    })
    .catch(err => {
        console.error(err) // о том, что делать с этой ошибкой, поговорим позже
        return Promise.resolve(3)
    })
    .then(res => {
        console.log(res) // 3
    })
    .catch(err => {
        // этот блок тут на тот случай, если в предыдущем блоке возникнет какая-нибудь ошибка
        console.error(err)
    })

▍Асинхронные механизмы и try…catch

После того, как в JavaScript появилась конструкция async/await, мы вернулись к классическому способу обработки ошибок — к try...catch...finally. Обрабатывать ошибки при таком подходе оказывается очень легко и удобно. Рассмотрим пример.

;(async function() {
    try {
        await someFuncThatThrowsAnError()
    } catch (err) {
        console.error(err) // об этом поговорим позже
    }

    console.log('Easy!') // будет выполнено
})()

При таком подходе ошибки в асинхронном коде обрабатываются так же, как в синхронном. В результате теперь, при необходимости, в одном блоке catch можно обрабатывать более широкий диапазон ошибок.

2. Генерирование и обработка ошибок в серверном коде

Теперь, когда у нас есть инструменты для работы с ошибками, посмотрим на то, что мы можем с ними делать в реальных ситуациях. Генерирование и правильная обработка ошибок — это важнейший аспект серверного программирования. Существуют разные подходы к работе с ошибками. Здесь будет продемонстрирован подход с использованием собственного конструктора для экземпляров объекта Error и кодов ошибок, которые удобно передавать во фронтенд или любым механизмам, использующим серверные API. Как структурирован бэкенд конкретного проекта — особого значения не имеет, так как при любом подходе можно использовать одни и те же идеи, касающиеся работы с ошибками.

В качестве серверного фреймворка, отвечающего за маршрутизацию, мы будем использовать Express.js. Подумаем о том, какая структура нам нужна для организации эффективной системы обработки ошибок. Итак, вот что нам нужно:

  1. Универсальная обработка ошибок — некий базовый механизм, подходящий для обработки любых ошибок, в ходе работы которого просто выдаётся сообщение наподобие Something went wrong, please try again or contact us, предлагающее пользователю попробовать выполнить операцию, давшую сбой, ещё раз или связаться с владельцем сервера. Эта система не отличается особой интеллектуальностью, но она, по крайней мере, способна сообщить пользователю о том, что что-то пошло не так. Подобное сообщение гораздо лучше, чем «бесконечная загрузка» или нечто подобное.
  2. Обработка конкретных ошибок — механизм, позволяющий сообщить пользователю подробные сведения о причинах неправильного поведения системы и дать ему конкретные советы по борьбе с неполадкой. Например, это может касаться отсутствия неких важных данных в запросе, который пользователь отправляет на сервер, или в том, что в базе данных уже существует некая запись, которую он пытается добавить ещё раз, и так далее.

▍Разработка собственного конструктора объектов ошибок

Здесь мы воспользуемся стандартным классом Error и расширим его. Пользоваться механизмами наследования в JavaScript — дело рискованное, но в данном случае эти механизмы оказываются весьма полезными. Зачем нам наследование? Дело в том, что нам, для того, чтобы код удобно было бы отлаживать, нужны сведения о трассировке стека ошибки. Расширяя стандартный класс Error, мы, без дополнительных усилий, получаем возможности по трассировке стека. Мы добавляем в наш собственный объект ошибки два свойства. Первое — это свойство code, доступ к которому можно будет получить с помощью конструкции вида err.code. Второе — свойство status. В него будет записываться код состояния HTTP, который планируется передавать клиентской части приложения.

Вот как выглядит класс CustomError, код которого оформлен в виде модуля.

class CustomError extends Error {
    constructor(code = 'GENERIC', status = 500, ...params) {
        super(...params)

        if (Error.captureStackTrace) {
            Error.captureStackTrace(this, CustomError)
        }

        this.code = code
        this.status = status
    }
}

module.exports = CustomError

▍Маршрутизация

Теперь, когда наш объект ошибки готов к использованию, нужно настроить структуру маршрутов. Как было сказано выше, нам требуется реализовать унифицированный подход к обработке ошибок, позволяющий одинаково обрабатывать ошибки для всех маршрутов. По умолчанию фреймворк Express.js не вполне поддерживает такую схему работы. Дело в том, что все его маршруты инкапсулированы.

Для того чтобы справиться с этой проблемой, мы можем реализовать собственный обработчик маршрутов и определять логику маршрутов в виде обычных функций. Благодаря такому подходу, если функция маршрута (или любая другая функция) выбрасывает ошибку, она попадёт в обработчик маршрутов, который затем может передать её клиентской части приложения. При возникновении ошибки на сервере мы планируем передавать её во фронтенд в следующем формате, полагая, что для этого будет применяться JSON-API:

{
    error: 'SOME_ERROR_CODE',
    description: 'Something bad happened. Please try again or contact support.'
}

Если на данном этапе происходящие кажется вам непонятным — не беспокойтесь — просто продолжайте читать, пробуйте работать с тем, о чём идёт речь, и постепенно вы во всём разберётесь. На самом деле, если говорить о компьютерном обучении, здесь применяется подход «сверху-вниз», когда сначала обсуждаются общие идеи, а потом осуществляется переход к частностям.

Вот как выглядит код обработчика маршрутов.

const express = require('express')
const router = express.Router()
const CustomError = require('../CustomError')

router.use(async (req, res) => {
    try {
        const route = require(`.${req.path}`)[req.method]

        try {
            const result = route(req) // Передаём запрос функции route
            res.send(result) // Передаём клиенту то, что получено от функции route
        } catch (err) {
            /*
            Сюда мы попадаем в том случае, если в функции route произойдёт ошибка
            */
            if (err instanceof CustomError) {
                /* 
                Если ошибка уже обработана - трансформируем её в 
                возвращаемый объект
                */

                return res.status(err.status).send({
                    error: err.code,
                    description: err.message,
                })
            } else {
                console.error(err) // Для отладочных целей

                // Общая ошибка - вернём универсальный объект ошибки
                return res.status(500).send({
                    error: 'GENERIC',
                    description: 'Something went wrong. Please try again or contact support.',
                })
            }
        }
    } catch (err) {
        /* 
         Сюда мы попадём, если запрос окажется неудачным, то есть,
         либо не будет найдено файла, соответствующего пути, переданному
         в запросе, либо не будет экспортированной функции с заданным
         методом запроса
        */
        res.status(404).send({
            error: 'NOT_FOUND',
            description: 'The resource you tried to access does not exist.',
        })
    }
})

module.exports = router

Полагаем, комментарии в коде достаточно хорошо его поясняют. Надеемся, читать их удобнее, чем объяснения подобного кода, данные после него.

Теперь взглянем на файл маршрутов.

const CustomError = require('../CustomError')

const GET = req => {
    // пример успешного выполнения запроса
    return { name: 'Rio de Janeiro' }
}

const POST = req => {
    // пример ошибки общего характера
    throw new Error('Some unexpected error, may also be thrown by a library or the runtime.')
}

const DELETE = req => {
    // пример ошибки, обрабатываемой особым образом
    throw new CustomError('CITY_NOT_FOUND', 404, 'The city you are trying to delete could not be found.')
}

const PATCH = req => {
    // пример перехвата ошибок и использования CustomError
    try {
        // тут случилось что-то нехорошее
        throw new Error('Some internal error')
    } catch (err) {
        console.error(err) // принимаем решение о том, что нам тут делать

        throw new CustomError(
            'CITY_NOT_EDITABLE',
            400,
            'The city you are trying to edit is not editable.'
        )
    }
}

module.exports = {
    GET,
    POST,
    DELETE,
    PATCH,
}

В этих примерах с самими запросами ничего не делается. Тут просто рассматриваются разные сценарии возникновения ошибок. Итак, например, запрос GET /city попадёт в функцию const GET = req =>..., запрос POST /city попадёт в функцию const POST = req =>... и так далее. Эта схема работает и при использовании параметров запросов. Например — для запроса вида GET /city?startsWith=R. В целом, здесь продемонстрировано, что при обработке ошибок, во фронтенд может попасть либо общая ошибка, содержащая лишь предложение попробовать снова или связаться с владельцем сервера, либо ошибка, сформированная с использованием конструктора CustomError, которая содержит подробные сведения о проблеме.
Данные общей ошибки придут в клиентскую часть приложения в таком виде:

{
    error: 'GENERIC',
    description: 'Something went wrong. Please try again or contact support.'
}

Конструктор CustomError используется так:

throw new CustomError('MY_CODE', 400, 'Error description')

Это даёт следующий JSON-код, передаваемый во фронтенд:

{
    error: 'MY_CODE',
    description: 'Error description'
}

Теперь, когда мы основательно потрудились над серверной частью приложения, в клиентскую часть больше не попадают бесполезные логи ошибок. Вместо этого клиент получает полезные сведения о том, что пошло не так.

Не забудьте о том, что здесь лежит репозиторий с рассматриваемым здесь кодом. Можете его загрузить, поэкспериментировать с ним, и, если надо, адаптировать под нужды вашего проекта.

3. Работа с ошибками на клиенте

Теперь пришла пора описать третью часть нашей системы обработки ошибок, касающуюся фронтенда. Тут нужно будет, во-первых, обрабатывать ошибки, возникающие в клиентской части приложения, а во-вторых, понадобится оповещать пользователя об ошибках, возникающих на сервере. Разберёмся сначала с показом сведений о серверных ошибках. Как уже было сказано, в этом примере будет использована библиотека React.

▍Сохранение сведений об ошибках в состоянии приложения

Как и любые другие данные, ошибки и сообщения об ошибках могут меняться, поэтому их имеет смысл помещать в состояние компонентов. При монтировании компонента данные об ошибке сбрасываются, поэтому, когда пользователь впервые видит страницу, там сообщений об ошибках не будет.

Следующее, с чем надо разобраться, заключается в том, что ошибки одного типа нужно показывать в одном стиле. По аналогии с сервером, здесь можно выделить 3 типа ошибок.

  1. Глобальные ошибки — в эту категорию попадают сообщения об ошибках общего характера, приходящие с сервера, или ошибки, которые, например, возникают в том случае, если пользователь не вошёл в систему и в других подобных ситуациях.
  2. Специфические ошибки, выдаваемые серверной частью приложения — сюда относятся ошибки, сведения о которых приходят с сервера. Например, подобная ошибка возникает, если пользователь попытался войти в систему и отправил на сервер имя и пароль, а сервер сообщил ему о том, что пароль неправильный. Подобные вещи в клиентской части приложения не проверяются, поэтому сообщения о таких ошибках должны приходить с сервера.
  3. Специфические ошибки, выдаваемые клиентской частью приложения. Пример такой ошибки — сообщение о некорректном адресе электронной почты, введённом в соответствующее поле.

Ошибки второго и третьего типов очень похожи, работать с ними можно, используя хранилище состояния компонентов одного уровня. Их главное различие заключается в том, что они исходят из разных источников. Ниже, анализируя код, мы посмотрим на работу с ними.

Здесь будет использоваться встроенная в React система управления состоянием приложения, но, при необходимости, вы можете воспользоваться и специализированными решениями для управления состоянием — такими, как MobX или Redux.

▍Глобальные ошибки

Обычно сообщения о таких ошибках сохраняются в компоненте наиболее высокого уровня, имеющем состояние. Они выводятся в статическом элементе пользовательского интерфейса. Это может быть красное поле в верхней части экрана, модальное окно или что угодно другое. Реализация зависит от конкретного проекта. Вот как выглядит сообщение о такой ошибке.

Сообщение о глобальной ошибке

Теперь взглянем на код, который хранится в файле Application.js.

import React, { Component } from 'react'

import GlobalError from './GlobalError'

class Application extends Component {
    constructor(props) {
        super(props)

        this.state = {
            error: '',
        }

        this._resetError = this._resetError.bind(this)
        this._setError = this._setError.bind(this)
    }

    render() {
        return (
            <div className="container">
                <GlobalError error={this.state.error} resetError={this._resetError} />
                <h1>Handling Errors</h1>
            </div>
        )
    }

    _resetError() {
        this.setState({ error: '' })
    }

    _setError(newError) {
        this.setState({ error: newError })
    }
}

export default Application

Как видно, в состоянии, в Application.js, имеется место для хранения данных ошибки. Кроме того, тут предусмотрены методы для сброса этих данных и для их изменения.

Ошибка и метод для сброса ошибки передаётся компоненту GlobalError, который отвечает за вывод сообщения об ошибке на экран и за сброс ошибки после нажатия на значок x в поле, где выводится сообщение. Вот код компонента GlobalError (файл GlobalError.js).

import React, { Component } from 'react'

class GlobalError extends Component {
    render() {
        if (!this.props.error) return null

        return (
            <div
                style={{
                    position: 'fixed',
                    top: 0,
                    left: '50%',
                    transform: 'translateX(-50%)',
                    padding: 10,
                    backgroundColor: '#ffcccc',
                    boxShadow: '0 3px 25px -10px rgba(0,0,0,0.5)',
                    display: 'flex',
                    alignItems: 'center',
                }}
            >
                {this.props.error}
                 
                <i
                    className="material-icons"
                    style={{ cursor: 'pointer' }}
                    onClick={this.props.resetError}
                >
                    close
                </font></i>
            </div>
        )
    }
}

export default GlobalError

Обратите внимание на строку if (!this.props.error) return null. Она указывает на то, что при отсутствии ошибки компонент ничего не выводит. Это предотвращает постоянный показ красного прямоугольника на странице. Конечно, вы, при желании, можете поменять внешний вид и поведение этого компонента. Например, вместо того, чтобы сбрасывать ошибку по нажатию на x, можно задать тайм-аут в пару секунд, по истечении которого состояние ошибки сбрасывается автоматически.

Теперь, когда всё готово для работы с глобальными ошибками, для задания глобальной ошибки достаточно воспользоваться _setError из Application.js. Например, это можно сделать в том случае, если сервер, после обращения к нему, вернул сообщение об общей ошибке (error: 'GENERIC'). Рассмотрим пример (файл GenericErrorReq.js).

import React, { Component } from 'react'
import axios from 'axios'

class GenericErrorReq extends Component {
    constructor(props) {
        super(props)

        this._callBackend = this._callBackend.bind(this)
    }

    render() {
        return (
            <div>
                <button onClick={this._callBackend}>Click me to call the backend</button>
            </div>
        )
    }

    _callBackend() {
        axios
            .post('/api/city')
            .then(result => {
                // сделать что-нибудь с результатом в том случае, если запрос оказался успешным
            })
            .catch(err => {
                if (err.response.data.error === 'GENERIC') {
                    this.props.setError(err.response.data.description)
                }
            })
    }
}

export default GenericErrorReq

На самом деле, на этом наш разговор об обработке ошибок можно было бы и закончить. Даже если в проекте нужно оповещать пользователя о специфических ошибках, никто не мешает просто поменять глобальное состояние, хранящее ошибку и вывести соответствующее сообщение поверх страницы. Однако тут мы не остановимся и поговорим о специфических ошибках. Во-первых, это руководство по обработке ошибок иначе было бы неполным, а во-вторых, с точки зрения UX-специалистов, неправильно будет показывать сообщения обо всех ошибках так, будто все они — глобальные.

▍Обработка специфических ошибок, возникающих при выполнении запросов

Вот пример специфического сообщения об ошибке, выводимого в том случае, если пользователь пытается удалить из базы данных город, которого там нет.

Сообщение о специфической ошибке

Тут используется тот же принцип, который мы применяли при работе с глобальными ошибками. Только сведения о таких ошибках хранятся в локальном состоянии соответствующих компонентов. Работа с ними очень похожа на работу с глобальными ошибками. Вот код файла SpecificErrorReq.js.

import React, { Component } from 'react'
import axios from 'axios'

import InlineError from './InlineError'

class SpecificErrorRequest extends Component {
    constructor(props) {
        super(props)

        this.state = {
            error: '',
        }

        this._callBackend = this._callBackend.bind(this)
    }

    render() {
        return (
            <div>
                <button onClick={this._callBackend}>Delete your city</button>
                <InlineError error={this.state.error} />
            </div>
        )
    }

    _callBackend() {
        this.setState({
            error: '',
        })

        axios
            .delete('/api/city')
            .then(result => {
                // сделать что-нибудь с результатом в том случае, если запрос оказался успешным
            })
            .catch(err => {
                if (err.response.data.error === 'GENERIC') {
                    this.props.setError(err.response.data.description)
                } else {
                    this.setState({
                        error: err.response.data.description,
                    })
                }
            })
    }
}

export default SpecificErrorRequest

Тут стоит отметить, что для сброса специфических ошибок недостаточно, например, просто нажать на некую кнопку x. То, что пользователь прочёл сообщение об ошибке и закрыл его, не помогает такую ошибку исправить. Исправить её можно, правильно сформировав запрос к серверу, например — введя в ситуации, показанной на предыдущем рисунке, имя города, который есть в базе. В результате очищать сообщение об ошибке имеет смысл, например, после выполнения нового запроса. Сбросить ошибку можно и в том случае, если пользователь внёс изменения в то, что будет использоваться при формировании нового запроса, то есть — при изменении содержимого поля ввода.

▍Ошибки, возникающие в клиентской части приложения

Как уже было сказано, для хранения данных о таких ошибках можно использовать состояние тех же компонентов, которое используется для хранения данных по специфическим ошибкам, поступающим с сервера. Предположим, мы позволяем пользователю отправить на сервер запрос на удаление города из базы только в том случае, если в соответствующем поле ввода есть какой-то текст. Отсутствие или наличие текста в поле можно проверить средствами клиентской части приложения.

В поле ничего нет, мы сообщаем об этом пользователю

Вот код файла SpecificErrorFrontend.js, реализующий вышеописанный функционал.

import React, { Component } from 'react'
import axios from 'axios'

import InlineError from './InlineError'

class SpecificErrorRequest extends Component {
    constructor(props) {
        super(props)

        this.state = {
            error: '',
            city: '',
        }

        this._callBackend = this._callBackend.bind(this)
        this._changeCity = this._changeCity.bind(this)
    }

    render() {
        return (
            <div>
                <input
                    type="text"
                    value={this.state.city}
                    style={{ marginRight: 15 }}
                    onChange={this._changeCity}
                />
                <button onClick={this._callBackend}>Delete your city</button>
                <InlineError error={this.state.error} />
            </div>
        )
    }

    _changeCity(e) {
        this.setState({
            error: '',
            city: e.target.value,
        })
    }

    _validate() {
        if (!this.state.city.length) throw new Error('Please provide a city name.')
    }

    _callBackend() {
        this.setState({
            error: '',
        })

        try {
            this._validate()
        } catch (err) {
            return this.setState({ error: err.message })
        }

        axios
            .delete('/api/city')
            .then(result => {
                // сделать что-нибудь с результатом в том случае, если запрос оказался успешным
            })
            .catch(err => {
                if (err.response.data.error === 'GENERIC') {
                    this.props.setError(err.response.data.description)
                } else {
                    this.setState({
                        error: err.response.data.description,
                    })
                }
            })
    }
}

export default SpecificErrorRequest

▍Интернационализация сообщений об ошибках с использованием кодов ошибок

Возможно, сейчас вы задаётесь вопросом о том, зачем нам нужны коды ошибок (наподобие GENERIC), если мы показываем пользователю только сообщения об ошибках, полученных с сервера. Дело в том, что, по мере роста и развития приложения, оно, вполне возможно, выйдет на мировой рынок, а это означает, что настанет время, когда создателям приложения нужно будет задуматься о поддержке им нескольких языков. Коды ошибок позволяют отличать их друг от друга и выводить сообщения о них на языке пользователя сайта.

Итоги

Надеемся, теперь у вас сформировалось понимание того, как можно работать с ошибками в веб-приложениях. Нечто вроде console.error(err) следует использовать только в отладочных целях, в продакшн подобные вещи, забытые программистом, проникать не должны. Упрощает решение задачи логирования использование какой-нибудь подходящей библиотеки наподобие loglevel.

Уважаемые читатели! Как вы обрабатываете ошибки в своих проектах?

  • Reply with quote

[Solved] STATUS_OBJECT_NAME_NOT_FOUND

Hi,
I have just install Vbox 5.2.26 after using 5.2.16 for many months (have been a Vbox user for over 11 years). My 4 vms (2xXP, 2xWin7) no longer load, message is identical: NtCreateFile(DeviceVBoxDrvStub) failed: 0xc0000034 STATUS_OBJECT_NAME_NOT_FOUND (0 retries). Have read the topic on hardening and other postings on this error message (mostly very old) but did not see anything relevant. Logs attached. Any suggestions?
Regards,
Michael

VBox.log
(116.47 KiB) Downloaded 87 times

mikhail51
 
Posts: 5
Joined: 11. Apr 2019, 11:51

  • Reply with quote

Re: STATUS_OBJECT_NAME_NOT_FOUND

Postby mpack » 11. Apr 2019, 12:48

The provided VBox.log is from 5.2.16, so not very useful.

The STATUS_OBJECT_NAME_NOT_FOUND error makes me believe that VirtualBox has not been installed properly, and components are missing. VirtualBox must be installed as the correct user (i.e. not the Admin user), using «Run as administrator» to give you explicit rights to install device drivers and make registry changes.

mpack
Site Moderator
 
Posts: 37957
Joined: 4. Sep 2008, 17:09
Primary OS: MS Windows 10
VBox Version: PUEL
Guest OSses: Mostly XP

  • Reply with quote

Re: STATUS_OBJECT_NAME_NOT_FOUND

Postby mikhail51 » 11. Apr 2019, 13:21

Thanks for your suggestion, mpack. However I installed the new version while logged in as systems administrator (SOP). The vbox.log attached was the latest version after first few attempts to start vm so I guess 5.2.26 did not write to the log before each failure.

More information:
a) I did a windows restore — this was unsuccessful as the restored version client would not load — I could not start the Vbox UI. Undoing the restore gave me the UI back (v5.2.26) with vm still failing to start
b) I now notice that VMM shows the vm system drive as unattached — while the vm settings still show the drive as attached. Using settings to delete the system disk and re-attach did not work, VMM still showed the drive as unattached and the vm failed to start, giving the same error.
Regards,
MIchael

scrCap-000065-17.png
scrCap-000065-17.png (5.41 KiB) Viewed 15215 times

scrCap-000066-17.png
scrCap-000066-17.png (8.61 KiB) Viewed 15215 times
mikhail51
 
Posts: 5
Joined: 11. Apr 2019, 11:51

  • Reply with quote

Re: STATUS_OBJECT_NAME_NOT_FOUND

Postby mikhail51 » 11. Apr 2019, 14:15

I’ve subsequently tried creating a new vm using a copy of an existing system drive. I used 2 slightly different methods, once specifying an existing drive when creating the vm and once adding a system drive to a vm created without drives. Each time, the same failure occurred and VMM shows the system drive as ‘not attached’.
Regards, Michael

mikhail51
 
Posts: 5
Joined: 11. Apr 2019, 11:51

  • Reply with quote

Re: STATUS_OBJECT_NAME_NOT_FOUND

Postby mpack » 11. Apr 2019, 14:33

mikhail51 wrote:Thanks for your suggestion, mpack. However I installed the new version while logged in as systems administrator (SOP).

Which is precisely what I said is the mistake.

mpack
Site Moderator
 
Posts: 37957
Joined: 4. Sep 2008, 17:09
Primary OS: MS Windows 10
VBox Version: PUEL
Guest OSses: Mostly XP

  • Reply with quote

Re: STATUS_OBJECT_NAME_NOT_FOUND

Postby mikhail51 » 11. Apr 2019, 15:06

I also created 2 new vms (using two different pathways in the UI). Again, the system drives showed in VMM as ‘not attached’.

mikhail51
 
Posts: 5
Joined: 11. Apr 2019, 11:51

  • Reply with quote

Re: STATUS_OBJECT_NAME_NOT_FOUND

Postby andyp73 » 11. Apr 2019, 15:23

mikhail51 wrote:I also created 2 new vms (using two different pathways in the UI).

It doesn’t matter how many new guests you create and how many different paths you use through the UI. If the core VirtualBox application isn’t installed properly then it isn’t going to work. It is that plain and simple.

You should uninstall VirtualBox and make sure the C:Program FilesOracleVirtualBox directory has been deleted. Re-install VirtualBox using your current user, as mpack suggested, right clicking the installer and selecting «Run as Administrator». Once we are convinced that you have VirtualBox successfully installed then we can look at any guest issues.

-Andy.

My crystal ball is currently broken. If you want assistance you are going to have to give me all of the necessary information.
Please don’t ask me to do your homework for you, I have more than enough of my own things to do.

andyp73
Volunteer
 
Posts: 1642
Joined: 25. May 2010, 23:48
Primary OS: Mac OS X other
VBox Version: PUEL
Guest OSses: Assorted Linux, Windows Server 2012, DOS, Windows 10, BIOS/UEFI emulation

  • Reply with quote

Re: STATUS_OBJECT_NAME_NOT_FOUND

Postby mikhail51 » 11. Apr 2019, 16:17

Thanks mpack, Andy. Eyeballs quicker than brain, second time around read your post correctly, mpack. Before seeing your post, Andy, I reran the download file which presented a ‘Repair’ option. That seemed to work, my vm’s ran fine. So no major guest issues. Weirdly, VMM still shows the system drives as ‘not attached’ even though they work. But that issue doesn’t affect me so far. I’ll do the delete and complete re-install only if I run into issues. However, if I delete VirtualBox, will my vm’s, snapshots etc in %HOMEPATH%..etc be retained or will I have to recreate all vms? Thanks again.

mikhail51
 
Posts: 5
Joined: 11. Apr 2019, 11:51

  • Reply with quote

Re: STATUS_OBJECT_NAME_NOT_FOUND

Postby andyp73 » 11. Apr 2019, 16:22

mikhail51 wrote:However, if I delete VirtualBox, will my vm’s, snapshots etc in %HOMEPATH%..etc be retained

Yes, all of your guest configurations and virtual disk image files will be absolutely fine — the VirtualBox uninstaller doesn’t remove them. It wouldn’t matter as you have backups of that directory just in case…

-Andy.

My crystal ball is currently broken. If you want assistance you are going to have to give me all of the necessary information.
Please don’t ask me to do your homework for you, I have more than enough of my own things to do.

andyp73
Volunteer
 
Posts: 1642
Joined: 25. May 2010, 23:48
Primary OS: Mac OS X other
VBox Version: PUEL
Guest OSses: Assorted Linux, Windows Server 2012, DOS, Windows 10, BIOS/UEFI emulation

  • Reply with quote

Re: STATUS_OBJECT_NAME_NOT_FOUND

Postby mpack » 11. Apr 2019, 16:32

Thanks for reporting back. I’ll mark the topic as «solved».

mpack
Site Moderator
 
Posts: 37957
Joined: 4. Sep 2008, 17:09
Primary OS: MS Windows 10
VBox Version: PUEL
Guest OSses: Mostly XP


Return to VirtualBox on Windows Hosts

Who is online

Users browsing this forum: No registered users and 28 guests

Our internal developers have created a web app to allow customers to download documents. 
Their design uses Filestream to store and retrieve the documents; this is working for them in the development environment, but is failing in the production environment.

There are two main differences between Prod and Dev and neither one really seems to be causing the error:

  1. The production database is hosted on a clustered SQL server (MS Failover clustering) and the development database is a standalone SQL server. 
    In one of our desperate attempts to find the root cause we substituted a standalone SQL server in production and still got the same error so it seems unlikely that clustering itself is causing the problem.
  2. The development environment exists entirely within one network zone (i.e. direct connection between web servers and database cluster, no physical firewall in between). 
    In the production environment the web servers are in the DMZ and the database cluster is in the extranet zone, separated by a physical firewall. 
    The appropriate firewall rules (physical and Windows) are in place to allow the necessary communication between the web servers and the SQL cluster (tcp ports 139, 445 and 1433). 
    From both web servers I have used the MS PortQry utility and have verified that they can in fact communicate with the SQL cluster over the three specified ports; monitoring of the physical firewall has also confirmed connectivity, so it seems unlikely
    that the firewall is the issue.

Things I’ve verified so far:

The SQL cluster is configured to use Windows integrated auth.

Filestream is enabled (Full access) and configured on the SQL cluster. 
The Filestream group was created and I can see the ‘DatabasePRD_FileStream’ directory on the shared storage for the SQL cluster, there are currently over 4000 files stored there just waiting to be accessed.

The service account for the app pool the application is using has change/modify rights on the Filestream fileshare and has db_reader/db_writer perms on the SQL database.

I have installed NetMon on the active node in the SQL cluster to help diagnose the problem and 
I can see the incoming SMB connection from the web server; it connects and then appears to request a file with the following name:

File=v02-A60EC2F8-2B24-11DF-9CC3-AF2E56D89593DatabasePRDdboFIL_FilesDataE85436FB-D49C-4BB6-AB84-9779B6DC747DVolumeHint-HarddiskVolume861b11f38b5f34000befd3167e4dbfcb8@#4520

The server immediately returns a status of: STATUS_OBJECT_NOT_FOUND; which is true, that path does not exist. 
In fact I would expect a path more along the lines of:

DatabasePRD_Filestream 50b1875a-59b8-4578-b2ff-2c960693691f772cbb11-b827-445d-b039-cba986b31581000002e-0000ac3c-0002

To me it almost seems like there should be a translation between the “File=v02….” String and the actual path, but for some reason it is not occurring and the server just returns the not found status.

Has anyone else ever encountered this error?  If so what am I missing or what did we do wrong?  What do I need to change to make this work properly?

Thanks in advance,  Michael

NetMon capture for reference:

4518       12:58:31 PM 6/4/2012   
26.4292714         
System 192.168.1.163     192.168.86.79    
SMB2    SMB2:C   TREE CONNECT (0x3), Path=\SQLSERVERMSSQLSERVER
            {SMBOverTCP:226, TCP:225, IPv4:18}

4519       12:58:31 PM 6/4/2012   
26.4293010         
System 192.168.86.79     192.168.1.163    
SMB2    SMB2:R   TREE CONNECT (0x3), TID=0x1
  {SMBOverTCP:226, TCP:225, IPv4:18}

4520       12:58:31 PM 6/4/2012   
26.4307240         
System 192.168.1.163     192.168.86.79    
SMB2    SMB2:C   CREATE (0x5), Sh(RWD), DHnQ+ExtA+MxAc+QFid+RqLs(RHW), File=v02-A60EC2F8-2B24-11DF-9CC3-AF2E56D89593DatabasePRDdboFIL_FilesDataE85436FB-D49C-4BB6-AB84-9779B6DC747DVolumeHint-HarddiskVolume861b11f38b5f34000befd3167e4dbfcb8@#4520 
            
{SMB2:227, SMBOverTCP:226, TCP:225, IPv4:18}

4521       12:58:31 PM 6/4/2012   
26.4308786         
System 192.168.86.79     192.168.1.163    
SMB2    SMB2:R  — NT Status: System — Error, Code = (58) STATUS_OBJECT_PATH_NOT_FOUND 
CREATE (0x5) , File=v02-A60EC2F8-2B24-11DF-9CC3-AF2E56D89593GalaxyPRDdboFIL_FilesDataE85436FB-D49C-4BB6-AB84-9779B6DC747DVolumeHint-HarddiskVolume861b11f38b5f34000befd3167e4            
{SMB2:227, SMBOverTCP:226, TCP:225, IPv4:18}

Having upgraded Oracle Virtualbox on a Windows host, I found that I could not start any Virtual Machine instances. VirtualBox displays a dialogue window with the error message shown below,

Examining the virtual machine log file, i.e., LogsVBoxHardening.log in the Virtual Machine instance directory, I found the following error message near the end of the log file,


1660.6c38: supR3HardenedVmProcessInit: Opening vboxdrv stub...
1660.6c38: Error opening VBoxDrvStub:  STATUS_OBJECT_NAME_NOT_FOUND
1660.6c38: supR3HardenedWinReadErrorInfoDevice: NtCreateFile -> 0xc0000034
1660.6c38: Error -101 in supR3HardenedWinReSpawn! (enmWhat=3)
1660.6c38: NtCreateFile(DeviceVBoxDrvStub) failed: 0xc0000034 STATUS_OBJECT_NAME_NOT_FOUND (0 retries)

Driver is probably stuck stopping/starting. Try 'sc.exe query vboxdrv' to get more information about its state. Rebooting may actually help.

The message indicates that Windows service vboxdrv, i.e., the Virtual Box Driver has some problem. Following the advice in the log, I issue sc.exe query vboxdrv in a Windows Command Prompt window,


C:>sc start vboxdrv
[SC] StartService: OpenService FAILED 1060:

The specified service does not exist as an installed service.
C:>

which indicates that Windows service vboxdrv does not exist. To fix the issue, I reinstalled the driver vboxdrv from the directory C:Program FilesOracleVirtualBoxdriversvboxdrv. From the Windows GUI, you can right click the VBoxDrv.inf and choose «install».

If the installation is successful and you query the driver status again, you should see something similar below,


C:>sc query vboxdrv

SERVICE_NAME: vboxdrv
        TYPE               : 1  KERNEL_DRIVER
        STATE              : 4  RUNNING
                                (STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN)
        WIN32_EXIT_CODE    : 0  (0x0)
        SERVICE_EXIT_CODE  : 0  (0x0)
        CHECKPOINT         : 0x0
        WAIT_HINT          : 0x0
C:>

Now I can launch the virtual machine instances.

Понравилась статья? Поделить с друзьями:
  • Status error bitrix environment
  • Status err 0xc0010001 flash tool как исправить
  • Status device power failure как исправить на windows 10
  • Status device data error
  • Status da selection error 0xc0030003 flashtool ошибка