I finished reading through the code for the http-errors
package. I now understand how it works under the hood and it feels great. Not so much because it was confusing but because this is my second reverse engineering endeavor and it went way faster and I was a lot more confident with my approach. I can now take my reverse engineering hat off and get back to finishing the final project in the web security section of The Odin Project’s Node.js
course.
I have outlined the package below with some more details than my original notes. There were some things that the documentation was not the most clear on. Reading through the code and then reading the documentation helped to make sense of a few things.
The biggest thing that I am now becoming more conscious of is backwards compatibility. I am a JavaScript developer that prefers not using ;
semicolons at the end of my statements let alone using var
. In these older packages there is so much consideration for backwards compatibility. I wonder if I would need to use older syntax if I were to write a package today or if I would just need to maintain functionality from today moving forward. It is stressful to think about maintenance concerns and versioning issues. I have had this problem working with some Microsoft tools like dotnet
. It is not fun mapping what works with which version.
TLDR;
Okay, so here are the highlights of what I did:
- Backend -> Paused working on the Members Only Project from the web security section. Finished reading through the code base for the
http-errors
package and updating my notes on the package. I now have a stronger understanding of what the package does and what I can do it. For my current project, the package is not that helpful but I can see it helping with bigger apps.
Notes on http-errors
Package
What is the http-errors
package?
The http-errors
package is a convenience library. It contains tools that make it easier to construct errors in popular Node.js
frameworks like Express
, Koa
, and Connect
. (I wonder if there are other frameworks it can be used with or if it can be used as a stand-alone package as well.)
What does the http-errors
package do?
The package provides us with a function that returns an error object. These returned error objects are customized by the package to correspond with HTTP Response errors (400 – 599 HTTP status codes) when called with the appropriate arguments.
How Do You Use The http-errors
package?
After importing the package. Call the function with an HTTP status code or status name that corresponds with a valid HTTP error status. The function will then return the corresponding error object that can then be passed to our application’s error handler.
We can also add an optional message to replace the default message on the returned Error object by passing in the message as a second parameter. There are multiple ways to call the function that effect the returned Error object:
createError([status], [message], [properties])
createError([status], [error], [properties])
Lastly, we can construct our HTTP Error object by directly calling one of the corresponding Constructors. There are roughly 80 that exist on the imported function object as added methods.
new createError([msg]))
Create a new error object with the given message msg
. The error object inherits from createError.HttpError
.
var err = new createError.NotFound()
// OR
var err = new createError.[404]()
code
- the status code as a numbername
- the name of the error as a "bumpy case", i.e.NotFound
orInternalServerError
.
Install
This is a Node.js module available through the npm
registry. Installation is done using the npm
install command:
$ npm install http-errors
Example
var createError = require("http-errors");
var express = require("express");
var app = express();
app.use(function (req, res, next) {
if (!req.user)
return next(createError(401, "Please login to view this page."));
next();
});
API
This is the current API, currently extracted from Koa and subject to change.
There are multiple ways to call the function that effect the returned Error object:
createError([status], [message], [properties])
createError([status], [error], [properties])
Returned Error Object Properties
expose
- can be used to signal if message should be sent to the client, defaulting tofalse
when status>= 500
headers
- can be an object of header names to values to be sent to the client, defaulting toundefined
. When defined, the key names should all be lower-casedmessage
- the traditional error message, which should be kept short and all single linestatus
- the status code of the error, mirroringstatusCode
for general compatibilitystatusCode
- the status code of the error, defaulting to500
createError([status], [message], [properties])
Create a new error object with the given message msg
. The error object inherits from createError.HttpError
.
var err = createError(404, "This video does not exist!");
status: 500
- the status code as a numbermessage
- the message of the error, defaulting to node's text for that status code.properties
- custom properties to attach to the object
createError([status], [error], [properties])
Extend the given error object with createError.HttpError
properties. This will not alter the inheritance of the given error object, and the modified error object is the return value.
fs.readFile("foo.txt", function (err, buf) {
if (err) {
if (err.code === "ENOENT") {
var httpError = createError(404, err, { expose: false });
} else {
var httpError = createError(500, err);
}
}
});
status
- the status code as a numbererror
- the error object to extendproperties
- custom properties to attach to the object
createError.isHttpError(val)
Determine if the provided val
is an HttpError
. This will return true
if the error inherits from the HttpError
constructor of this module or matches the "duck type" for an error this module creates. All outputs from the createError
factory will return true
for this function, including if an non-HttpError
was passed into the factory.
new createError([msg]))
Create a new error object with the given message msg
. The error object inherits from createError.HttpError
.
var err = new createError.NotFound();
code
- the status code as a numbername
- the name of the error as a "bumpy case", i.e.NotFound
orInternalServerError
.
List of all constructors
Below is a list of Status Code
's and Constructor Name
's:
400
=BadRequest
401
=Unauthorized
402
=PaymentRequired
403
=Forbidden
404
=NotFound
405
=MethodNotAllowed
406
=NotAcceptable
407
=ProxyAuthenticationRequired
408
=RequestTimeout
409
=Conflict
410
=Gone
411
=LengthRequired
412
=PreconditionFailed
413
=PayloadTooLarge
414
=URITooLong
415
=UnsupportedMediaType
416
=RangeNotSatisfiable
417
=ExpectationFailed
418
=ImATeapot
421
=MisdirectedRequest
422
=UnprocessableEntity
423
=Locked
424
=FailedDependency
425
=TooEarly
426
=UpgradeRequired
428
=PreconditionRequired
429
=TooManyRequests
431
=RequestHeaderFieldsTooLarge
451
=UnavailableForLegalReasons
500
=InternalServerError
501
=NotImplemented
502
=BadGateway
503
=ServiceUnavailable
504
=GatewayTimeout
505
=HTTPVersionNotSupported
506
=VariantAlsoNegotiates
507
=InsufficientStorage
508
=LoopDetected
509
=BandwidthLimitExceeded
510
=NotExtended
511
=NetworkAuthenticationRequired
Reverse Engineering The http-errors
Package
The package only has one file index.js
. It has a few dependencies
and quite a few devDependencies
.
Code Design
The code is designed with the main code execution written at the top and all the other functions are written at the bottom. This design style utilizes JavaScript
's hoisting functionality which allows for the variable and function declaration to be compiled prior to executing the code. If this functionality did not exist or was not utilized the main code execution could have been written before the function and variable declarations in the file.
What Happens on Import
When we import the package:
- All the package's dependencies are imported for use in the only module (
index.js
).
depd
packagesetprototypeof
packagestatuses
packagestatuses
packageinherits
packagetoidentifier
package
- The
module.exports
property is assigned the value of thecreateError
binding.createError
is the binding for a function which is declared later in the file but is hoisted for use earlier in the file. - Custom properties are then added to the
createError
function prior to being exported.
HttpError
isHttpError
- The
populateConstructorExports(exports, codes, HttpError)
function is called. This function manually adds constructed HTTP Error object Constructor functions as methods to theexports
argument. The methods correspond with the HTTP status codes and names provided. In our case, we are using the HTTP response error status codes provided by thestatuses
package. The function adds one method matching the status code and one method matching the name of the status code. Each receives the same Error object Constructor as it's value. As a result there are more than 80 methods added to theexports
object (i.e. thecreateError
function) once thepopulateConstructorExports
function has completed executing. These are the values being passed in when the function is called:
exports = module.exports = createError
codes = statuses.codes
HttpError = module.exports.HttpError = createHttpErrorConstructor()
- Finally when we call the function we will get an customized HTTP Error (if we pass in a valid status code / name) or a generic Error object returned. This can then be passed on to our error handler in our application.
populateConstructorExports(module.exports, statuses.codes, module.exports.HttpError)
Exports from Package
The package exports a function (createError
) but it also seems to add properties to the Function object. Here is the organization of what I have first understood to be function with added properties that we get when we import the package.
[Function: createError] {
'400': [Function: BadRequestError],
'401': [Function: UnauthorizedError],
'402': [Function: PaymentRequiredError],
'403': [Function: ForbiddenError],
'404': [Function: NotFoundError],
'405': [Function: MethodNotAllowedError],
'406': [Function: NotAcceptableError],
'407': [Function: ProxyAuthenticationRequiredError],
'408': [Function: RequestTimeoutError],
'409': [Function: ConflictError],
'410': [Function: GoneError],
'411': [Function: LengthRequiredError],
'412': [Function: PreconditionFailedError],
'413': [Function: PayloadTooLargeError],
'414': [Function: URITooLongError],
'415': [Function: UnsupportedMediaTypeError],
'416': [Function: RangeNotSatisfiableError],
'417': [Function: ExpectationFailedError],
'418': [Function: ImATeapotError],
'421': [Function: MisdirectedRequestError],
'422': [Function: UnprocessableEntityError],
'423': [Function: LockedError],
'424': [Function: FailedDependencyError],
'425': [Function: TooEarlyError],
'426': [Function: UpgradeRequiredError],
'428': [Function: PreconditionRequiredError],
'429': [Function: TooManyRequestsError],
'431': [Function: RequestHeaderFieldsTooLargeError],
'451': [Function: UnavailableForLegalReasonsError],
'500': [Function: InternalServerError],
'501': [Function: NotImplementedError],
'502': [Function: BadGatewayError],
'503': [Function: ServiceUnavailableError],
'504': [Function: GatewayTimeoutError],
'505': [Function: HTTPVersionNotSupportedError],
'506': [Function: VariantAlsoNegotiatesError],
'507': [Function: InsufficientStorageError],
'508': [Function: LoopDetectedError],
'509': [Function: BandwidthLimitExceededError],
'510': [Function: NotExtendedError],
'511': [Function: NetworkAuthenticationRequiredError],
HttpError: [Function: HttpError],
isHttpError: [Function: isHttpError],
BadRequest: [Function: BadRequestError],
Unauthorized: [Function: UnauthorizedError],
PaymentRequired: [Function: PaymentRequiredError],
Forbidden: [Function: ForbiddenError],
NotFound: [Function: NotFoundError],
MethodNotAllowed: [Function: MethodNotAllowedError],
NotAcceptable: [Function: NotAcceptableError],
ProxyAuthenticationRequired: [Function: ProxyAuthenticationRequiredError],
RequestTimeout: [Function: RequestTimeoutError],
Conflict: [Function: ConflictError],
Gone: [Function: GoneError],
LengthRequired: [Function: LengthRequiredError],
PreconditionFailed: [Function: PreconditionFailedError],
PayloadTooLarge: [Function: PayloadTooLargeError],
URITooLong: [Function: URITooLongError],
UnsupportedMediaType: [Function: UnsupportedMediaTypeError],
RangeNotSatisfiable: [Function: RangeNotSatisfiableError],
ExpectationFailed: [Function: ExpectationFailedError],
ImATeapot: [Function: ImATeapotError],
MisdirectedRequest: [Function: MisdirectedRequestError],
UnprocessableEntity: [Function: UnprocessableEntityError],
Locked: [Function: LockedError],
FailedDependency: [Function: FailedDependencyError],
TooEarly: [Function: TooEarlyError],
UpgradeRequired: [Function: UpgradeRequiredError],
PreconditionRequired: [Function: PreconditionRequiredError],
TooManyRequests: [Function: TooManyRequestsError],
RequestHeaderFieldsTooLarge: [Function: RequestHeaderFieldsTooLargeError],
UnavailableForLegalReasons: [Function: UnavailableForLegalReasonsError],
InternalServerError: [Function: InternalServerError],
NotImplemented: [Function: NotImplementedError],
BadGateway: [Function: BadGatewayError],
ServiceUnavailable: [Function: ServiceUnavailableError],
GatewayTimeout: [Function: GatewayTimeoutError],
HTTPVersionNotSupported: [Function: HTTPVersionNotSupportedError],
VariantAlsoNegotiates: [Function: VariantAlsoNegotiatesError],
InsufficientStorage: [Function: InsufficientStorageError],
LoopDetected: [Function: LoopDetectedError],
BandwidthLimitExceeded: [Function: BandwidthLimitExceededError],
NotExtended: [Function: NotExtendedError],
NetworkAuthenticationRequired: [Function: NetworkAuthenticationRequiredError]
}
References:
http-errors
API Docs - npmhttp-errors
- GitHub Repostatuses
package -npm
Docs- Adding custom properties to functions - Stack Overflow
Goal For Round 8 of the #100DaysofCode Challenge
This is my eighth round of the "#100daysofcode" challenge. I will be continuing my work from round five, six, and seven into round eight. I was working through the book "Cracking the Coding Interview" by Gayle Laakmann McDowell. My goal was to become more familiar with algorithms and data structures. This goal was derived from my goal to better understand operating systems and key programs that I use in the terminal regularly e.g. Git. This goal was in turn derived from my desire to better understand the fundamental tools used for coding outside of popular GUIs. This in turn was derived from my desire to be a better back-end developer.
I am currently putting a pause on the algorithm work to build some backend/full stack projects. I primarily want to improve my skills with the back-end from an implementation perspective. I have improved tremendously in terminal and CLI skills but I lost focus due to how abstract the algorithm concepts got. I wanted to work on things that were more tangible until I can get to a position where I could directly benefit from improving my algorithm skills and theoretical knowledge. So that's the focus right now. Build my backend skills and prove my full stack capabilities by building some dope projects.
Again, I still have no idea if my path is correct but I am walking down this road anyways. Worst case scenario I learn a whole bunch of stuff that will help me out on my own personal projects. Best case scenario I actually become one of those unicorn developers that go on to start a billion dollar company... You never know LOL.