I'm trying to deploy the FastAPI app + front end in one Docker container, so I would not want to add NginX or another web server as it'll complicate the setup.
My routes should look like this:
/ <-- should serve index.html
/{id} <-- should serve index.html, but only when {id} is int
/css/* <-- should serve files from css directory
/... <-- a few other static files (e.g. /service-worker.js)
/api/<whatever> <-- these are the fastAPI routes
So one solution is to match /{id} when it's int and fall-through when it's not. Alternatively if there's a way to just serve everything as static file, except /api calls, that would work too.
Currently I have something like this in my main.py:
#app.get('/api/items')
async def get_items():
return [{'id':1, 'name': 'a'}, {'id':2, 'name': 'b'}]
#app.get('/{id}')
async def root():
with open('html/index.html', 'r') as f:
return HTMLResponse(f.read())
app.mount("/", StaticFiles(directory="dist"), name="static")
But i'm getting 404 when i navigate to "/". I'm serving it with uvicorn main:app.
Any help is appreciated.
As documented, you need to "mount" a static file path. You can set a folder with the name you like and add a prefix on the URL.
It'll then refresh at every page change, so you'll need some way to store access tokens/session data in the client. Also, every single call will go from the client to the API.
Below the link to the documentation about static files:
https://fastapi.tiangolo.com/tutorial/static-files/#static-files
Related
I have a Tornado application that I want to host under a non-root location with nginx. So I have an nginx configuration that looks like
server {
listen 80;
server_name mysite.com;
location /myapp/ {
proxy_pass http://localhost:8888/;
}
}
I want the app to live at mysite.com/myapp/. With the current configuration all of the Handlers are routed to the correct url however all of the links used in the templates are wrong. For example
<a href='/'>Home</a>
links to mysite.com/ rather than mysite.com/myapp/. I'd also like the app to still work locally so I don't want /myapp/ hard-coded into the templates.
Is there a way to deal with this with either nginx or Tornado?
My solution thus far has been to add a convenience function to the template namespace
import tornado.ioloop
import tornado.web
import os
APPLICATION_ROOT = os.environ.get('MYAPP_ROOT')
class BaseHandler(tornado.web.RequestHandler):
def full_url(self, path, *args, **kwargs):
if path.startswith('/'):
path = path.lstrip('/')
return os.path.join(APPLICATION_ROOT, path)
else:
return path
def get_template_namespace(self):
"""Returns a dictionary to be used as the default template namespace.
May be overridden by subclasses to add or modify values.
The results of this method will be combined with additional
defaults in the `tornado.template` module and keyword arguments
to `render` or `render_string`.
"""
namespace = dict(
handler=self,
request=self.request,
current_user=self.current_user,
locale=self.locale,
_=self.locale.translate,
pgettext=self.locale.pgettext,
static_url=self.static_url,
xsrf_form_html=self.xsrf_form_html,
reverse_url=self.reverse_url,
full_url=self.full_url
)
namespace.update(self.ui)
return namespace
and set MYAPP_ROOT='/myapp/' as an environment variable and use
Home
in the templates.
Tornado doesn't have good support for applications that can be relocated to different paths like this - it's generally assumed that the path prefix is known and can be hard-coded. (The trailing slash in the nginx proxy_pass directive is significant - remove it and nginx passes the request through as-is to Tornado)
If you still want to use different paths depending on whether you're going through nginx or not, I would recommend a URL helper function as you have here. Other options include always using relative paths instead of absolute ones in your URLs (will probably require using /myapp/home and /home instead of /myapp/ and / for the home page), or having nginx rewrite your links as in this question
I am using Cloud 9 IDE to build a website. My goal is to serve a static website from the site root '/index.html' and so on. The content in this site will be regenerated on a schedule (daily in this example). At the '/admin' and '/api' endpoints I want to serve a couple of flask apps.
Because this is being built on the Cloud 9 IDE, I do not have access to the proxy server configuration. I have to serve everything to one port using the HTML protocol. uWSGI is capable of doing exactly this. I am struggling with my configuration file though:
#uwsgi.ini
[uwsgi]
static-index = index.html
static-map2 = /=/home/ubuntu/workspace/generated-site
static-map2 = /static=/home/ubuntu/workspace/static-assets
mount = /admin=admin.py
mount = /api=api.py
manage-script-name = true
master = true
processes = 5
socket=0.0.0.0:8080
protocol=http
Requests to /admin and /api work as expected returning a result or 404 error.
Requests to / and /index.html both return generated-site/index.html as expected.
A request to /no_exist.html returns 404 Not Found as expected.
My problem is with the second static-map2. A request to /static/test.html came back 404 Not Found (I put an html file there to test).
static-map2 keep the path portion of the URL for its search so the request /static/test.html will be mapped to the file
/home/ubuntu/workspace/static-assets/static/test.html
You most probably want the simple static-map which strips the path from the URL before mapping to the file-system. So a request to /static/test.html will search for the file
/home/ubuntu/workspace/static-assets/test.html
I really could not find any resource on this. So how can I seperate caching of views/functions than static files (i.e. .css,.js)?
I want to cache my static objects for a week, on the other hand I need to cache functions/views for only a few minutes.
When I do following
from flask.ext.cache import Cache
cache = Cache(config={'CACHE_TYPE': 'simple'})
cache.init_app(app)
#cache.cached(timeout=500)
def index():
return render_template('index.html')
then all views, objects' cache time is set to the same value, 500. How to go about it?
I would not server the static files from my python application but try to delegate that to the web server (nginx, apache... ).
Then you could set the time to expire through headers controlling how long should the browser cache them.
I have multiple FileFields in my django app, which can belong to different users.
I am looking for a good way to restrict access to files for user who aren't the owner of the file.
What is the best way to achieve this? Any ideas?
Unfortuanately #Mikko's solution cannot actually work on a production environment since django is not designed to serve files. In a production environment files need to be served by your HTTP server (e.g apache, nginx etc) and not by your application/django server (e.g uwsgi, gunicorn, mod_wsgi etc).
That's why restricting file acccess is not very easy: You need a way for your HTTP server to ask the application server if it is ok to serve a file to a specific user requesting it. As you can understand thiss requires modification to both your application and your http server.
The best solution to the above problem is django-sendfile (https://github.com/johnsensible/django-sendfile) which uses the X-SendFile mechanism to implement the above. I'm copying from the project's description:
This is a wrapper around web-server specific methods for sending files to web clients. This is useful when Django needs to check permissions associated files, but does not want to serve the actual bytes of the file itself. i.e. as serving large files is not what Django is made for.
To understand more about the senfile mechanism, please read this answer: Django - Understanding X-Sendfile
2018 Update: Please notice that django-sendfile does not seem to be maintained anymore; probably it should still be working however if you want a more modern package with similar functionality take a look at https://github.com/edoburu/django-private-storage as commenter #surfer190 proposes. Especially make sure that you implement the "Optimizing large file transfers" section; you actuallyu need this for all transfers not only for large files.
2021 Update: I'm returning to this answer to point out that although it hasn't been updated for like 4 years, the django-sendfile project still works great with the current Django version (3.2) and I'm actually using it for all my projects that require that particular functionality! There is also an actively-maintained fork now, django-sendfile2, which has improved Python 3 support and more extensive documentation.
If you need just moderate security, my approach would be the following:
1) When the user uploads the file, generate a hard to guess path for it. For example you can create a folder with a randomly generated name for each uploaded file in your /static folder. You can do this pretty simply using this sample code:
file_path = "/static/" + os.urandom(32).encode('hex') + "/" + file_name
In this way it will be very hard to guess where other users' files are stored.
2) In the database link the owner to the file. An example schema can be:
uploads(id, user_id, file_path)
3) Use a property for your FileFields in the model to restrict access to the file, in this way:
class YourModel(models.Model)
_secret_file = models.FileField()
def get_secret_file(self):
# check in db if the user owns the file
if True:
return self._secret_file
elif:
return None # or something meaningful depanding on your app
secret_file = property(get_secret_file)
This is best handled by the server, e.g. nginx secure link module (nginx must be compiled with --with-http_secure_link_module)
Example from the documentation:
location /some-url/ {
secure_link $arg_md5,$arg_expires;
secure_link_md5 "$secure_link_expires$uri$remote_addr some-secret";
if ($secure_link = "") {
return 403;
}
if ($secure_link = "0") {
return 410;
}
if ($secure_link = "1") {
// authorised...
}
}
The file would be accessed like:
/some-url/some-file?md5=_e4Nc3iduzkWRm01TBBNYw&expires=2147483647
(This would be both time-limited and bound to the user at that IP address).
Generating the token to pass to the user would use something like:
echo -n 'timestamp/some-url/some-file127.0.0.1 some-secret' | \
openssl md5 -binary | openssl base64 | tr +/ -_ | tr -d =
Generally, you do not route private files through normal static file serving directly through Apache, Nginx or whatever web server you are using. Instead write a custom Django view which handles the permission checking and then returns the file as streaming download.
Make sure files are in a special private folder folder and not exposed through Django's MEDIA_URL or STATIC_URL
Write a view which will
Check that the user has access to the file in your view logic
Open the file with Python's open()
Return HTTP response which gets the file's handle as the parameter http.HttpResponse(_file, content_type="text/plain")
For example see download() here.
For those who use Nginx as a webserver to serve the file, the 'X-Accel-Redirect' is a good choice.
At the first, request for access to the file comes to Django and after authentication and authorization, it redirects internally to Nginx with 'X-Accel-Redirect'. more about this header: X-Accel-Redirect
The request comes to Django and will be checked like below:
if user_has_right_permission
response = HttpResponse()
# Let nginx guess to correct file mime-type by setting
# below header empty. otherwise all the files served as
# plain text
response['Content-Type'] = ''
response['X-Accel-Redirect'] = path_to_file
return response
else:
raise PermissionDenied()
If the user has the right permission, it redirects to Nginx to serve the file.
The Nginx config is like this:
server {
listen 81;
listen [::]:81;
...
location /media/ {
internal; can be accessed only internally
alias /app/media/;
}
...
}
Note: The thing about the path_to_file is that it should be started with "/media/" to serve by Nginx (is clear though)
My question is about how to serve multiple urls.py (like urls1.py, urls2.py and so on) files in a single Django project.
I am using Win7 x64, django 1.4.1, python 2.7.3 and as a server django dev-server tool.
I have decided to use a method which i found by google from
http://effbot.org/zone/django-multihost.htm
I have created a multihost.py file and put in to the django middleware folder:
C:\python27\Lib\site-packages\django\middleware\multihost.py
With the following code:
from django.conf import settings
from django.utils.cache import patch_vary_headers
class MultiHostMiddleware:
def process_request(self, request):
try:
host = request.META["HTTP_HOST"]
if host[-3:] == ":80":
host = host[:-3] # ignore default port number, if present
request.urlconf = settings.HOST_MIDDLEWARE_URLCONF_MAP[host]
except KeyError:
pass # use default urlconf (settings.ROOT_URLCONF)
def process_response(self, request, response):
if getattr(request, "urlconf", None):
patch_vary_headers(response, ('Host',))
return response
Also in my project setting.py file i have added a mapping dictionary like the link above shows:
# File: settings.py
HOST_MIDDLEWARE_URLCONF_MAP = {
"mysite1.com": "urls1",
#"mysite2.com": "urls2"
}
I did not yet implemented the error handling like described by the link above.
My hosts file includes the follwoing:
127.0.0.1 mysite1.com
Project structure is the following:
effbot django project folder:
+ effbot
--- settings.py
--- view.py
--- wsgi.py
--- urls.py
--- urls1.py
+ objex
--- models.py
--- views.py
+ static
--- css
--- images
There is no templates folder, because i dont serve html items from files, they are coming from databsse (i doubt that my problem is in this).
Now the problem is: when i go for the adress
mysite1.com
in my browser with django dev-server launched i get code 301 from the server. And browser shows "cannot display page" message.
Could you please explain me how to use mentioned method? I'm new to django and haven't done any real projects yet. Just have read the docs and launched a couple of sites at home to learn how it works.
I expect that urlconfs will be called in dependance from incoming
request.META["HTTP_HOST"]
The target is to serve different urlconfs for mysite1.com and mysite2.com
in a single django project.
I think this should to work some how.
Thank you for any feedback.
EDIT:
After some research attempts i found that i plugged my multyhost.py incorrectly in settings.
Fixed now. But the same result still.
Also i found out that my django dev-server tool is not reflecting anyhow that it handles any requests from the browser (IE9) except when i do "http://127.0.0.1".
May be i have to try some production server for my task, like nginx?
Your ROOT_URLCONF should be effbot.urls without .pyas you can see in the example in the documentation.
Also, HOST_MIDDLEWARE_URLCONF_MAP should reflect the ROOT_URLCONF so add `effbot. like this:
HOST_MIDDLEWARE_URLCONF_MAP = {
"mysite1.com": "effbot.urls1",
#"mysite2.com": "effbot.urls2"
}
One more thing, please try with another browser (Chrome, Firefox), sometimes I had problems accessing dev server with IE.