@@ -87,31 +87,29 @@ export default class Request {
8787 public readonly method : string ;
8888
8989 /**
90- * This property is much like `req.url`; however, it retains the original request URL,
91- * allowing you to rewrite `req.url` freely for internal routing purposes. For example,
92- * the "mounting" feature of `app.use()` will rewrite `req.url` to strip the mount
93- * point.
90+ * This property is much like `req.url`; however, it always retains the original URL
91+ * from the event that triggered the request, allowing you to rewrite `req.url` freely
92+ * for internal routing purposes. For example, the "mounting" feature of `app.use()`
93+ * will rewrite `req.url` to strip the mount point:
9494 *
95- * TODO: We still don't support internal re-routing mid-request. We need to investigate
96- * how, exactly, Express does this, and what it would take to support it.
97- *
98- * ```
99- * // GET /search?q=something
100- * req.originalUrl
101- * // => "/search?q=something"
10295 * ```
96+ * // GET 'http://www.example.com/admin/new'
10397 *
104- * In a middleware function, `req.originalUrl` is a combination of `req.baseUrl` and
105- * `req.path`, as shown in the following example.
98+ * let router = new Router();
10699 *
107- * ```
108- * app.use('/admin', function(req, res, next) { // GET 'http://www.example.com/admin/new'
100+ * router.get('/new', function(req, res, next) {
109101 * console.log(req.originalUrl); // '/admin/new'
110102 * console.log(req.baseUrl); // '/admin'
111103 * console.log(req.path); // '/new'
104+ * console.log(req.url); // '/new'
112105 * next();
113106 * });
107+ *
108+ * app.addSubRouter('/admin', router);
114109 * ```
110+ *
111+ * `req.originalUrl` stays the same even when a route handler changes `req.url` for
112+ * internal re-routing. See `req.url` for an example of internal re-routing.
115113 */
116114 public readonly originalUrl : string ;
117115
@@ -142,24 +140,6 @@ export default class Request {
142140 */
143141 public readonly params : Readonly < StringMap > ;
144142
145- /**
146- * Contains the path part of the request URL.
147- *
148- * ```
149- * // example.com/users?sort=desc
150- * req.path // => "/users"
151- * ```
152- *
153- * When called from a middleware, the mount point is not included in req.path. See
154- * `req.originalUrl` for more details.
155- */
156- public readonly path : string ;
157-
158- /**
159- * Synonymous with `req.path`.
160- */
161- public readonly url : string ;
162-
163143 /**
164144 * Contains the request protocol string: either `http` or (for TLS requests) `https`
165145 * (always lowercase).
@@ -226,10 +206,6 @@ export default class Request {
226206 */
227207 public readonly eventSourceType : ( 'ALB' | 'APIGW' ) ;
228208
229- // TODO: maybe some of those properties should not be read-only ... for example, how
230- // would some middleware do the equivalent of an internal redirect? How does Express
231- // handle that?
232-
233209 /**
234210 * The body of the request. If the body is an empty value (e.g. `''`), `req.body` will
235211 * be `null` to make body-exists checks (e.g. `if (req.body)`) simpler.
@@ -239,10 +215,29 @@ export default class Request {
239215 */
240216 public body ?: unknown ;
241217
218+ protected _parentRequest ?: Request ;
219+ protected _url : string ;
220+ protected _path : string ;
221+
242222 private readonly _headers : StringArrayOfStringsMap ;
243223 private readonly _event : RequestEvent ;
244224
245- public constructor ( app : Application , event : RequestEvent , context : HandlerContext , baseURL : string = '' , params : StringMap = { } ) {
225+ public constructor ( app : Application , eventOrRequest : RequestEvent | Request , context : HandlerContext ,
226+ baseURL : string = '' , params : StringMap = { } ) {
227+ let event : RequestEvent ,
228+ path : string ;
229+
230+ if ( eventOrRequest instanceof Request ) {
231+ // Make this request a sub-request of the request passed into the constructor
232+ this . _parentRequest = eventOrRequest ;
233+ path = this . _parentRequest . path . substring ( baseURL . length ) ;
234+ baseURL = this . _parentRequest . baseUrl + baseURL ;
235+ event = this . _parentRequest . _event ;
236+ } else {
237+ event = eventOrRequest ;
238+ path = event . path ;
239+ }
240+
246241 this . app = app ;
247242 this . _event = event ;
248243 this . _headers = this . _parseHeaders ( event ) ;
@@ -267,19 +262,108 @@ export default class Request {
267262
268263 // Fields related to routing:
269264 this . baseUrl = baseURL ;
270- this . path = this . url = event . path ;
271- this . originalUrl = baseURL + event . path ;
265+ this . _path = this . _url = path ;
266+ // Despite the fact that the Express docs say that the `originalUrl` is `baseUrl +
267+ // path`, it's actually always equal to the original URL that initiated the request.
268+ // If, for example, a route handler changes the `url` of a request, the `path` is
269+ // changed too, *but* `originalUrl` stays the same. This would not be the case if
270+ // `originalUrl = `baseUrl + path`. See the documentation on the `url` getter for
271+ // more details.
272+ this . originalUrl = event . path ;
272273 this . params = Object . freeze ( params ) ;
273274 }
274275
276+ /** PUBLIC PROPERTIES: GETTERS AND SETTERS */
277+
278+ /**
279+ * `req.url` is the same as `req.path` in most cases.
280+ *
281+ * However, route handlers and other middleware may change the value of `req.url` to
282+ * redirect the request to other registered middleware. For example:
283+ *
284+ * ```
285+ * // GET example.com/admin/users/1337
286+ *
287+ * const router1 = new express.Router(),
288+ * router2 = new express.Router();
289+ *
290+ * router1.get('/users/:userID', function(req, res, next) {
291+ * // ...
292+ * if (req.params.userID === authenticatedUser.id) {
293+ * // User ID is the same as the authenticated user's. Re-route to user profile
294+ * // handler:
295+ * req.url = '/profile';
296+ * return next();
297+ * }
298+ * // ...
299+ * });
300+ *
301+ * router2.get('/profile', function(req, res) {
302+ * console.log(req.originalUrl); // '/admin/users/1337'
303+ * console.log(req.baseUrl); // '/admin'
304+ * console.log(req.path); // '/profile'
305+ * console.log(req.url); // '/profile'
306+ * // ...
307+ * });
308+ *
309+ * app.addSubRouter('/admin', router1);
310+ * app.addSubRouter('/admin', router2);
311+ * ```
312+ *
313+ * In the example above, the `GET` request to `/admin/users/1337` is re-routed to the
314+ * `/profile` handler in `router2`. Any other route handlers on `router1` that would
315+ * have handled the `/users/1337` route are skiped. Also, notice that `req.url` keeps
316+ * the value given to it by `router1`'s route handler, but `req.originalUrl` stays the
317+ * same.
318+ *
319+ * If the route handler or middleware that changes `req.url` adds a query string to
320+ * `req.url`, the query string is retained on the `req.url` property but the query
321+ * string keys and values are *not* parsed and `req.params` is *not* updated. This
322+ * follows Express' apparent behavior when handling internal re-routing with URLs that
323+ * contain query strings.
324+ */
325+ public get url ( ) : string {
326+ return this . _url ;
327+ }
328+
329+ public set url ( url : string ) {
330+ url = url || '' ;
331+
332+ // Update the parent request's URL with the new URL value
333+ if ( this . _parentRequest ) {
334+ let indexOfCurrentURL = this . _parentRequest . url . length - this . _url . length ;
335+
336+ this . _parentRequest . url = this . _parentRequest . url . substring ( 0 , indexOfCurrentURL ) + url ;
337+ }
338+ this . _url = url ;
339+ // Remove query parameters from the URL to form the new path
340+ this . _path = url . split ( '?' ) [ 0 ] ;
341+ }
342+
343+ /**
344+ * Contains the path part of the request URL.
345+ *
346+ * ```
347+ * // example.com/users?sort=desc
348+ * req.path // => "/users"
349+ * ```
350+ *
351+ * When referenced from middleware, the mount point is not included in `req.path`. See
352+ * `req.originalUrl` for more details.
353+ *
354+ * When any middleware changes the value of `req.url` for internal re-routing,
355+ * `req.path` is updated also. See `req.url` for an example of internal re-routing.
356+ */
357+ public get path ( ) : string {
358+ return this . _path ;
359+ }
360+
361+ // Disable changing the `path` via the public API by not implementing a setter here.
362+
275363 /** CLONING FUNCTION */
276364
277365 public makeSubRequest ( baseURL : string , params ?: StringMap ) : Request {
278- const restOfPath = this . _event . path . substring ( baseURL . length ) ,
279- // TODO: this isn't a deep clone - does it need to be?
280- event = _ . extend ( { } , this . _event , { path : restOfPath } ) ;
281-
282- return new Request ( this . app , event , this . context , baseURL , params ) ;
366+ return new Request ( this . app , this , this . context , baseURL , params ) ;
283367 }
284368
285369 /** CONVENIENCE FUNCTIONS */
0 commit comments