Chalice: A method for getting the base url

Created on 20 Aug 2017  路  7Comments  路  Source: aws/chalice

For some usecases, it would be great to be able to find the base URL for the chalice deployment itself. E.g. if returning documents that links to different url's for more information on objects.

This is especially helpful when dealing using chalice to serve HTML back. Something like _app.get_base_url()._

E.g. if i request https://lambda.mydomain.tld/v1/prod/overview and this is served by:

@app.route('/overview')
def overview():

I can then in the overview function use _app.get_base_url()_ to get https://lambda.mydomain.tld/v1/prod/

Alternatively, if that is too difficult, i would be fine if i could at least get /v1/prod returned.

I couldn't find any such function in the documentation.

feature-request

Most helpful comment

@jamesls Apologize for these questions, but I am trying to familiarize myself with everything here.

I am thinking of making this a @property on the Request class.

And the plan is to calculate it by something like:

# Resolve the resourcePath, enabling comparison to the path
resolved_resource_path = self.context['resourcePath']
for key in self.uri_params.keys():
    resolved_resource_path = resolved_resource_path.replace('{%s}' % key, self.uri_params[key])

# Take the path and discard the application resource path
base_path = self.context['path'][:self.context['path'].rfind(resolved_resource_path)]

# Get Scheme and Host from headers
scheme_and_host = '%s://%s' % (self.headers['x-forwarded-proto'], self.headers['host'])

# If using a non-standard port, append it to the scheme and host
if self.headers['x-forwarded-proto'].lower() == 'https' and self.headers['x-forwarded-port'] != 443:
    scheme_and_host += ':%s' % self.headers['x-forwarded-port']

if self.headers['x-forwarded-proto'].lower() == 'http' and self.headers['x-forwarded-port'] != 80:
    scheme_and_host += ':%s' % self.headers['x-forwarded-port']

# Return base path
return '%s/%s' % (scheme_and_host, base_path)

Some of this might seem a bit overkill, but here is my thinking:

  • Setting the port is useful for chalice local which does not use the standard port
  • Resolving the resource path and using that instead of stage is necessary for when using custom domains (and since they allow not setting a stage, which then becomes part of the url, we cannot simply assume that anything about the first / being magical)

If this approach seems reasonable to you, i will try and add some tests, and submit a pull request. If not, i would appreciate a hint in the right direction.

I have read the contributing guide, so i should be on top of that.

All 7 comments

We could add that to the current request object, since that's the thing that has the necessary information. FWIW, you can do this now, there's just no convenience method:

from chalice import Chalice

app = Chalice(app_name='foo')
app.debug = True


def get_base_url(current_request):
    headers = current_request.headers
    return '%s://%s/%s' % (headers['x-forwarded-proto'],
                           headers['host'],
                           current_request.context['stage'])


@app.route('/')
def index():
    base_url = get_base_url(app.current_request)
    return base_url + '/other-view'


@app.route('/other-view')
def other_view():
    return app.current_request.to_dict()

Thanks @jamesls
That would make a lot of sense. I can try and do a pull request if that is useful?

I think it would be useful, feel free to send a pull request. Our Contributing Guide has info on contributing changes. Let me know if you have any questions.

@jamesls Apologize for these questions, but I am trying to familiarize myself with everything here.

I am thinking of making this a @property on the Request class.

And the plan is to calculate it by something like:

# Resolve the resourcePath, enabling comparison to the path
resolved_resource_path = self.context['resourcePath']
for key in self.uri_params.keys():
    resolved_resource_path = resolved_resource_path.replace('{%s}' % key, self.uri_params[key])

# Take the path and discard the application resource path
base_path = self.context['path'][:self.context['path'].rfind(resolved_resource_path)]

# Get Scheme and Host from headers
scheme_and_host = '%s://%s' % (self.headers['x-forwarded-proto'], self.headers['host'])

# If using a non-standard port, append it to the scheme and host
if self.headers['x-forwarded-proto'].lower() == 'https' and self.headers['x-forwarded-port'] != 443:
    scheme_and_host += ':%s' % self.headers['x-forwarded-port']

if self.headers['x-forwarded-proto'].lower() == 'http' and self.headers['x-forwarded-port'] != 80:
    scheme_and_host += ':%s' % self.headers['x-forwarded-port']

# Return base path
return '%s/%s' % (scheme_and_host, base_path)

Some of this might seem a bit overkill, but here is my thinking:

  • Setting the port is useful for chalice local which does not use the standard port
  • Resolving the resource path and using that instead of stage is necessary for when using custom domains (and since they allow not setting a stage, which then becomes part of the url, we cannot simply assume that anything about the first / being magical)

If this approach seems reasonable to you, i will try and add some tests, and submit a pull request. If not, i would appreciate a hint in the right direction.

I have read the contributing guide, so i should be on top of that.

@colde, I think we should move forward with your last proposal.

Not sure how up to date all of the information is here, but I was having a hard time using the get_base_url function for both local development and in the cloud so I had to make a few modifications:

def get_base_url(current_request):
    headers = current_request.headers
    base_url = '%s://%s' % (headers.get('x-forwarded-proto', 'http'), headers['host'])
    if 'stage' in current_request.context:
        base_url = '%s/%s' % (base_url, current_request.context.get('stage'))
    return base_url

In particular the x-forwarded-proto header and stage context are missing when running chalice locally.

Just to complicate things a bit more, when using a private API Gateway with a VPC endpoint we don't get the x-forwarded-proto header either:

private-service-test - DEBUG - Current Request headers=CaseInsensitiveMapping({
  'accept': '*/*',
  'accept-encoding': 'gzip, deflate, br',
  'content-type': 'application/json',
  'host': '123456-vpce-123456.execute-api.us-east-1.amazonaws.com',
  'user-agent': 'PostmanRuntime/7.26.5',
  'x-amzn-cipher-suite': 'ECDHE-RSA-AES128-GCM-SHA256',
  'x-amzn-tls-version': 'TLSv1.2',
  'x-amzn-vpc-id': 'vpc-123456',
  'x-amzn-vpce-config': '1',
  'x-amzn-vpce-id': 'vpce-123456',
  'x-forwarded-for': '172.24.128.161'
})
Was this page helpful?
0 / 5 - 0 ratings

Related issues

AtaruOhto picture AtaruOhto  路  3Comments

cdalar picture cdalar  路  4Comments

davidolmo picture davidolmo  路  3Comments

stannie picture stannie  路  4Comments

vrinda1410 picture vrinda1410  路  3Comments