Statische Dateien: Ubuntu + Bottle + Nginx + GUnicorn

Django, Flask, Bottle, WSGI, CGI…
Antworten
dd0815
User
Beiträge: 33
Registriert: Donnerstag 25. Juli 2019, 13:57

Hallo zusammen,

meine statischen Dateien werden leider in der oben genannten Konfiguration nicht gefunden...

Zuvor im Test unter Windows hatte es folgendermaßen funktioniert - eine Route für alle statischen Dateien sowie bei Templates der vollständige Pfad (was natürlich auch unschön ist):

Code: Alles auswählen

# Static Files
@app.route('/static/<filename:re:.*\.*>')
def send_lalacss(filename):
    return static_file(filename, root='static')

##### Main #####
@app.route('/hello')
def hello():
    ...
    return template('index', result=result, template_lookup=['c:/Daten/02_Projekte/Prj_008_Firma/50_Projekte/testBottle/static'] ) 
Nun direkt auf einem Ubuntu-Server sieht meine Testdatei sieht folgendermaßen aus:

Code: Alles auswählen

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import os
import mysql.connector
from mysql.connector import errorcode
import logging

import bottle
from bottle import Bottle, route, run, template, static_file
from bottle import get, post, request

@route('/static/<filepath:path>')
def hallo(filepath):
    #return static_file(filepath, root='/var/www/mserp/html/static/')
    return static_file(filepath, root='/var/www/mserp/html/')

# a basic URL route to test whether Bottle is responding properly
@route('/')
def index():
    result = [[1, 2, 3, "anl1", "Anlage 1"], [4, 5, 6, "anl2", "Anlage 2"]]
    return template('index', result=result, template_lookup=['/var/www/mserp/html/static/']) 

# these two lines are only used for python app.py
if __name__ == '__main__':
    run(host='0.0.0.0', port=8080, debug=True, reloader=True)

# this is the hook for Gunicorn to run Bottle
app = bottle.default_app()
Die Verzeichnisstruktur sieht folgendermaßen aus:

Code: Alles auswählen

/var/www/mserp/html/
├── bottle.py
├── static
│   ├── context.js
│   ├── images
│   │   ├── logo.png
│   │   └── suche.svg
│   ├── index.tpl
│   ├── main.js
│   └── mystyle.css
├── testApp2.py
Sollte nicht eigentlich bereits Nginx die statischen Dateien bereitstellen? Deshalb hatten wir extra eine Zeile in der Nginx-Serverblockkonfiguration eingefügt (in einem früheren Thema), hier nochmal die ganze Datei:

Code: Alles auswählen

server {
	listen 8080;
	listen [::]:8080;

	server_name _;

	# declar proxy params and values to forward to your gunicorn webserver
	proxy_pass_request_headers on;
	proxy_pass_request_body on;
	proxy_set_header Host $host;
	proxy_set_header X-Real-IP $remote_addr;
	proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
	proxy_read_timeout 120s;

	# 
	location /mserp {
		# try_files $uri $uri/ =404;
		
		# statische Daten	
		#root /var/www/mserp/static;
		#root /var/www/mserp/html/;
		root /var/www/mserp/html/static;
		
		# here is where you declare that every request to /
		# should be proxy to 127.0.0.1:8000 (which is where
		# your gunicorn will be running on)
		proxy_pass_header Server;
		proxy_set_header Host $http_host;
		proxy_redirect off;
		proxy_set_header X-Real-IP $remote_addr;
		proxy_set_header X-Scheme $scheme;
		proxy_connect_timeout 10;
		proxy_read_timeout 10;

		# the actual nginx directive to forward the request
		proxy_pass http://127.0.0.1:8000/;

	}
}
Hier mal ein Ausschnitt aus dem nginx-Access.log:

Code: Alles auswählen

172.21.6.18 - - [14/Oct/2019:10:48:37 +0000] "GET /mserp HTTP/1.1" 200 988 "-" "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:69.0) Gecko/20100101 Firefox/69.0"
172.21.6.18 - - [14/Oct/2019:10:48:37 +0000] "GET /static/mystyle.css HTTP/1.1" 404 152 "http://172.21.3.4:8080/mserp" "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:69.0) Gecko/20100101 Firefox/69.0"
172.21.6.18 - - [14/Oct/2019:10:48:37 +0000] "GET /static/main.js HTTP/1.1" 404 152 "http://172.21.3.4:8080/mserp" "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:69.0) Gecko/20100101 Firefox/69.0"
172.21.6.18 - - [14/Oct/2019:10:48:37 +0000] "GET /static/context.js HTTP/1.1" 404 152 "http://172.21.3.4:8080/mserp" "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:69.0) Gecko/20100101 Firefox/69.0"
172.21.6.18 - - [14/Oct/2019:10:48:37 +0000] "GET /static/images/logo.png HTTP/1.1" 404 152 "http://172.21.3.4:8080/mserp" "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:69.0) Gecko/20100101 Firefox/69.0"
172.21.6.18 - - [14/Oct/2019:10:48:37 +0000] "GET /static/context.js HTTP/1.1" 404 152 "http://172.21.3.4:8080/mserp" "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:69.0) Gecko/20100101 Firefox/69.0"
172.21.6.18 - - [14/Oct/2019:10:48:37 +0000] "GET /static/images/logo.png HTTP/1.1" 404 152 "http://172.21.3.4:8080/mserp" "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:69.0) Gecko/20100101 Firefox/69.0"
Und ein Ausschnitt aus dem nginx-Error.log:

Code: Alles auswählen

2019/10/14 10:48:37 [error] 33822#33822: *3 open() "/usr/share/nginx/html/static/main.js" failed (2: No such file or directory), client: 172.21.6.18, server: _, request: "GET /static/main.js HTTP/1.1", host: "172.21.3.4:8080", referrer: "http://172.21.3.4:8080/mserp"
2019/10/14 10:48:37 [error] 33822#33822: *4 open() "/usr/share/nginx/html/static/context.js" failed (2: No such file or directory), client: 172.21.6.18, server: _, request: "GET /static/context.js HTTP/1.1", host: "172.21.3.4:8080", referrer: "http://172.21.3.4:8080/mserp"
2019/10/14 10:48:37 [error] 33822#33822: *1 open() "/usr/share/nginx/html/static/images/logo.png" failed (2: No such file or directory), client: 172.21.6.18, server: _, request: "GET /static/images/logo.png HTTP/1.1", host: "172.21.3.4:8080", referrer: "http://172.21.3.4:8080/mserp"
2019/10/14 10:48:37 [error] 33822#33822: *1 open() "/usr/share/nginx/html/static/context.js" failed (2: No such file or directory), client: 172.21.6.18, server: _, request: "GET /static/context.js HTTP/1.1", host: "172.21.3.4:8080", referrer: "http://172.21.3.4:8080/mserp"
2019/10/14 10:48:37 [error] 33822#33822: *1 open() "/usr/share/nginx/html/static/images/logo.png" failed (2: No such file or directory), client: 172.21.6.18, server: _, request: "GET /static/images/logo.png HTTP/1.1", host: "172.21.3.4:8080", referrer: "http://172.21.3.4:8080/mserp"
Wieso sucht nginx im Verzeichnis /usr/share/nginx? Wo ist denn das konfiguriert?
Und wie kann man die Template-Pfade sinnvoll angeben (also nicht vollständig)?

Sorry für den langen Beitrag und viele Grüße,
dd0815
Benutzeravatar
noisefloor
User
Beiträge: 4181
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,
Wieso sucht nginx im Verzeichnis /usr/share/nginx? Wo ist denn das konfiguriert?
Das ist AFAIK der Default Wert von nginx.

Grundsätzlich solltest du _nie_ für Templates etc. absolute Pfade verwenden, weil das deine Anwendung schlecht portable macht. Templates erwartet Bottle im Unterverzeichnis `views`. Wenn du dich daran hältst, dann musst du keinen Pfad zum Template angeben.

Für statische Inhalte, die Bottle ausliefert, solltest du im Skript zuerst den absoluten Pfad feststellen, wo Bottle liegt und dann daraus den Pfad bauen, wo die statischen Dateien liegen, also z.B. /pfad/zu/deiner/app/ + static . Das geht z.B. mit dem `os.path` Modul.

Deine nginx Konfig ist so unvollständig. Du brauchst eine eigene Route für statische Dateien, also insgesamt mindestens zwei Route.
BTW: die Dateien müssen _nicht_ in /var/www/html liegen - du hast ja keine "klassische" Webseite. Die Dateien können auch z.B. im Homeverzeichnis liegen.
Und warum hast du Bottle in dem Verzeichnis liegen?

Gruß, noisefloor
dd0815
User
Beiträge: 33
Registriert: Donnerstag 25. Juli 2019, 13:57

Hallo noisefloor,

ich habe nun folgende Projektstruktur in meinem Homeverzeichnis angelegt:

Code: Alles auswählen

/home/dgaadmin/apps/mserp
├── bottle.py
├── static
│   ├── context.js
│   ├── logo.png
│   ├── main.js
│   └── suche.svg
├── testApp2.py
└── views
    └── index.tpl
Weil das bottle-Framework nur aus der einen Datei besteht und ich somit die aktuellste nehmen kann (0.13-dev), ist sie mit im Verzeichnis. Wenn ich sie weglasse, dann wird das Modul bottle nicht gefunden. Ich möchte Python3 benutzen und blicke mit den Paketen noch nicht ganz durch (das Bottle ist angeblich mit der Version 0.12.13-1 installiert, aber vielleicht nur für Python 2.7?). Deshalb habe ich mir jetzt auch gunicorn3 installiert, der automatisch das Python3 nimmt.

Genau, ich will ja die Anwendung portierbar machen. Das mit den Templates im views-Ordner scheint zu funktionieren, aber manchmal hab ich das Gefühl, dass der Cache auch ein Eigenleben hat. Ich weiß immer noch nicht, ob ein Neustart vom gunicorn reicht oder ob ich einfach nur die *.pyc-Dateien bzw. den Ordner __pycache__ löschen muss. Dann hilft es auch oft den Browser komplett neu zu starten...

Eigentlich sollte doch das Nginx die statischen Inhalte ausliefern und das gunicorn die dynamischen Inhalte, oder? Hier im Beispiel habe ich als statische Elemente eine Css, JavaScript und Bilder. Folgendes steht im Html im Browser:
<link type="text/css" href="/static/mystyle.css" rel="stylesheet">
Sieht doch erstmal richtig aus, nur dass Nginx eben mit dem Static noch nicht viel anfangen kann. Ich probiere jetzt mal folgende Konfigurationsdatei:

Code: Alles auswählen

server {
	listen 8080;
	listen [::]:8080;

	server_name _;

	# declar proxy params and values to forward to your gunicorn webserver
	proxy_pass_request_headers on;
	proxy_pass_request_body on;
	proxy_set_header Host $host;
	proxy_set_header X-Real-IP $remote_addr;
	proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
	proxy_read_timeout 120s;

	location /static {
		root /home/dgaadmin/apps/mserp/static/;
	}
	
	# 
	location /mserp {
		# try_files $uri $uri/ =404;
		
		# statische Daten	
		#root /var/www/mserp/html/static;
		#root /home/dgaadmin/apps/mserp/static/;
		
		# here is where you declare that every request to /
		# should be proxy to 127.0.0.1:8000 (which is where
		# your gunicorn will be running on)
		proxy_pass_header Server;
		proxy_set_header Host $http_host;
		proxy_redirect off;
		proxy_set_header X-Real-IP $remote_addr;
		proxy_set_header X-Scheme $scheme;
		proxy_connect_timeout 10;
		proxy_read_timeout 10;

		# the actual nginx directive to forward the request
		proxy_pass http://127.0.0.1:8000/;

	}
}
Sirius3
User
Beiträge: 18255
Registriert: Sonntag 21. Oktober 2012, 17:20

@dd0815: durch dieses wilde Herumraten bist Du sehr ineffizient. Das was in `root` steht, wird immer vor die komplette URL gehängt, aus `/static/mystyle.css` wird also `/home/dgaadmin/apps/mserp/static/static/mystyle.css`. Ich vermute mal, dort liegen keine Dateien.

Genauso ist es mit Deinem `proxy_pass`. Dort wird auch Deine gesamte URL weitergeleitet, also mit `/mserp`. Will man etwas anders, muß man rewrite-Rules definieren.

Das kann man alles in der Dokumentation zu nginx nachlesen, was Du dringend mal nachholen solltest, sonst stehst Du bei dem nächsten mini-Problem wieder hier.
Benutzeravatar
noisefloor
User
Beiträge: 4181
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,
und ich somit die aktuellste nehmen kann (0.13-dev), ist sie mit im Verzeichnis.
Das ist überhaupt kein Grund. Du kannst Bottle grundsätzlich in einer beliebigen Version a) systemweit (nicht empfohlen), b) für dich als Benutzer oder c) in einem virtual environment (empfohlen) via pip installieren. Und das ganze ginge sogar parallel für Python 2 und 3 (sofern man das wollte / bräuchte).
Ich weiß immer noch nicht, ob ein Neustart vom gunicorn reicht oder ob ich einfach nur die *.pyc-Dateien bzw. den Ordner __pycache__ löschen muss. Dann hilft es auch oft den Browser komplett neu zu starten...
Zum Entwickeln willst du gar nicht gunicorn oder was auch immer nehmen, sondern _immer_ den eingebauten Server von Bottle kombiniert mit dem Autoreloader. Und _erst_, wenn alles so läuft, wie du dir das vorstellst, geht es ans Deployment. Weil du sonst jedes Mal gunicorn stoppen und neu starten musst, damit geänderte Dateien gelesen werden.

Gruß, noisefloor
dd0815
User
Beiträge: 33
Registriert: Donnerstag 25. Juli 2019, 13:57

Hallo zusammen,

@Sirius3:

Ja, diese Ineffizienz ist mir auch schon aufgefallen :D , schließlich doktore ich ja schon eine Weile an den Projektbasics rum. Es kommt natürlich verschärfend dazu, vom Linux nicht wirklich viel Ahnung gehabt zu haben und musste somit viele Sachen gleichzeitig kennenzulernen. Insofern ist auch das Herumprobieren hilfreich, schließlich lernt der Mensch durch Trial&Error. Aber nicht zuletzt dank Euch ist das Projekt auf einem guten Weg, mittlerweile funktionieren die statischen Dateien. Dank des Nginx-Errorlogs hatte ich das doppelte /static/ im Pfad auch gesehen und geändert. Im Übrigen verstehe ich jetzt erst, was Du hiermit (im letzten Thema) meintest:
`location /` mußt Du jetzt durch `location /mserp` ersetzen, damit nur alles was zu mserp gehört an Python weitergeleitet wird, und Du noch andere Python-Services unter anderen Namen bedienen kannst.
Dann fehlt noch ein root, damit Du die Auslieferung von statischen Daten von den dynamischen trennen kannst.
Hier wäre ein kleines Stückchen Code viel hilfreicher gewesen als nicht verstandene Antworten, aber ich möchte mich natürlich nicht beschweren - ganz im Gegenteil, ich finde es toll, dass es Leute wie Euch gibt, die Ihr Wissen in solchen Foren weitergeben :idea: . Auch suche ich schon ziemlich lange im Netz nach Antworten, bevor ich hier oder irgendwo etwas frage (aber ich muss zugeben, die Nginx-Doku nicht gelesen zu haben :? ). Hier ist nun jedenfalls meine aktuelle Nginx-Konfigurationsdatei:

Code: Alles auswählen

server {
	listen 8080;
	listen [::]:8080;

	server_name _;

	# declar proxy params and values to forward to your gunicorn webserver
	proxy_pass_request_headers on;
	proxy_pass_request_body on;
	proxy_set_header Host $host;
	proxy_set_header X-Real-IP $remote_addr;
	proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
	proxy_read_timeout 120s;

	location /mserp {
		# try_files $uri $uri/ =404;
		
		# here is where you declare that every request to /
		# should be proxy to 127.0.0.1:8000 (which is where
		# your gunicorn will be running on)
		proxy_pass_header Server;
		proxy_set_header Host $http_host;
		proxy_redirect off;
		proxy_set_header X-Real-IP $remote_addr;
		proxy_set_header X-Scheme $scheme;
		proxy_connect_timeout 10;
		proxy_read_timeout 10;

		# the actual nginx directive to forward the request
		proxy_pass http://127.0.0.1:8000/;

	}
	
	location /static {
		root /home/dgaadmin/apps/mserp/;
	}
}
Was Du mit dem proxy_pass meintest, habe ich nicht verstanden. Aber so wie es jetzt ist, wollte ich es eigentlich haben. Ich habe nun die Anfänge einer vernünftigen Projektstruktur, die statischen Dateien werden gefunden und ich könnte weitere Applikationen erstellen (auch wenn ich natürlich die Sache mit dem Servernamen noch nicht geblickt habe) und ich habe endlich den Linuxserver, also das Produktivsystem, am Laufen. Nun kann ich mich auf die App konzentrieren...

@noisefloor:

wie gesagt, Bottle 012.13-1 ist installiert:
dgaadmin@172.21.3.4:~/apps/mserp$ apt-cache policy python-bottle
python-bottle:
Installiert: 0.12.13-1
Installationskandidat: 0.12.13-1
Versionstabelle:
*** 0.12.13-1 500
500 http://de.archive.ubuntu.com/ubuntu bionic/universe amd64 Packages
100 /var/lib/dpkg/status
aber wenn ich Datei weglasse, meckert das gunicorn beim starten rum:
dgaadmin@172.21.3.4:~/apps/mserp$ gunicorn3 -w 4 testApp2:app
[2019-10-15 08:42:38 +0000] [40057] [INFO] Starting gunicorn 19.7.1
[2019-10-15 08:42:38 +0000] [40057] [INFO] Listening at: http://127.0.0.1:8000 (40057)
[2019-10-15 08:42:38 +0000] [40057] [INFO] Using worker: sync
[2019-10-15 08:42:38 +0000] [40072] [INFO] Booting worker with pid: 40072
[2019-10-15 08:42:38 +0000] [40072] [ERROR] Exception in worker process
Traceback (most recent call last):
File "/usr/lib/python3/dist-packages/gunicorn/arbiter.py", line 578, in spawn_worker
worker.init_process()
File "/usr/lib/python3/dist-packages/gunicorn/workers/base.py", line 126, in init_process
self.load_wsgi()
File "/usr/lib/python3/dist-packages/gunicorn/workers/base.py", line 135, in load_wsgi
self.wsgi = self.app.wsgi()
File "/usr/lib/python3/dist-packages/gunicorn/app/base.py", line 67, in wsgi
self.callable = self.load()
File "/usr/lib/python3/dist-packages/gunicorn/app/wsgiapp.py", line 65, in load
return self.load_wsgiapp()
File "/usr/lib/python3/dist-packages/gunicorn/app/wsgiapp.py", line 52, in load_wsgiapp
return util.import_app(self.app_uri)
File "/usr/lib/python3/dist-packages/gunicorn/util.py", line 377, in import_app
__import__(module)
File "/home/dgaadmin/apps/mserp/testApp2.py", line 10, in <module>
import bottle
ModuleNotFoundError: No module named 'bottle'
[2019-10-15 08:42:38 +0000] [40072] [INFO] Worker exiting (pid: 40072)
[2019-10-15 08:42:38 +0000] [40057] [INFO] Shutting down: Master
[2019-10-15 08:42:38 +0000] [40057] [INFO] Reason: Worker failed to boot.
... und dann bin ich mit meinem bescheidenen Linux-Latein wieder am Ende :?

Zum Entwickeln nehme ich ja auch nicht den Linux-Server, sondern meine lokale Windowsversion mit dem eingebauten Webserver, aber ich wollte das Livesystem auch schon mal testen, nicht das ich nachher bei der Portierung wieder Wochen brauche...

Ich muss jetzt auf dem Ubuntu-Server noch Zugriff auf unser Firmenlaufwerk hinbekommen (Samba), per ODBC auf mehrere Datenbanken auf Windows-Servern zugreifen, für eine lokale MySQL-DB noch das phpmyAdmin zum Laufen bringen und für einen Kollegen noch irgendwie php-Skripte ausführen (also in meiner Applikation auf einen Link klicken), die nächsten Wochen :roll: bleiben spannend :D .

Viele Grüße,
dd0815
Benutzeravatar
__blackjack__
User
Beiträge: 14012
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Das installierte Bottle ist für Python 2. Immer wenn da nur ``python-*`` im Paketnamen steht, solltest Du davon ausgehen, dass das für Python 2 ist. Die für Python 3 fangen alle mit ``python3-*` an.
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
dd0815
User
Beiträge: 33
Registriert: Donnerstag 25. Juli 2019, 13:57

Danke __blackjack__, nun gehts auch ohne die bottle.py im Verzeichnis...
Antworten