Z sqlalchemy jak dynamicznie powiązać silnik bazy danych na żądanie
Mam aplikację internetową opartą na pylonach, która łączy się poprzez Sqlalchemy (v0.5) z bazą danych Postgres. Dla bezpieczeństwa, zamiast postępować zgodnie z typowym wzorem prostych aplikacji internetowych (jak widać w prawie wszystkich samouczkach), nie używam ogólnego użytkownika Postgres (np. "webapp"), ale wymagam od użytkowników wprowadzenia własnego identyfikatora użytkownika Postgres i hasła i używam go do nawiązania połączenia. Oznacza to, że w pełni korzystamy z Postgres Ochrona.
Komplikując sprawy jeszcze bardziej, istnieją dwie oddzielne bazy danych do połączenia. Chociaż obecnie znajdują się w tym samym klastrze Postgres, muszą być w stanie przenieść się do oddzielnych hostów w późniejszym terminie.
Używamy pakietu deklaratywnego sqlalchemy, choć nie widzę, żeby to miało jakikolwiek związek z tą sprawą.
Większość przykładów SQLAlchemy pokazuje trywialne podejścia, takie jak konfigurowanie metadanych raz, podczas uruchamiania aplikacji, z ogólnym database userid i hasło, które jest używane przez aplikację webową. Zwykle odbywa się to za pomocą metadanych.bind = create_engine (), czasami nawet na poziomie modułu w plikach modelu bazy danych.
Moje pytanie brzmi, jak możemy odroczyć ustanowienie połączeń do czasu zalogowania się użytkownika, a następnie (oczywiście) ponownie wykorzystać te połączenia, lub ponownie ustanowić je przy użyciu tych samych danych uwierzytelniających, dla każdego kolejnego żądania.
Mamy to działa -- myślimy -- ale nie tylko nie jestem pewien jeśli chodzi o bezpieczeństwo, myślę również, że wygląda niesamowicie ciężko jak na tę sytuację.
Wewnątrz metody __call__
Basecontrollera pobieramy identyfikator użytkownika i hasło z sesji sieci Web, wywołujemy SQLAlchemy create_engine() raz dla każdej bazy danych, a następnie wywołujemy procedurę wywołującą sesję.bind_mapper () wielokrotnie, raz dla każdej tabeli, do której może się odwoływać na każdym z tych połączeń, nawet jeśli każde żądanie zwykle odwołuje się tylko do jednej lub dwóch tabel. Wygląda coś takiego:
# in lib/base.py on the BaseController class
def __call__(self, environ, start_response):
# note: web session contains {'username': XXX, 'password': YYY}
url1 = 'postgres://%(username)s:%(password)s@server1/finance' % session
url2 = 'postgres://%(username)s:%(password)s@server2/staff' % session
finance = create_engine(url1)
staff = create_engine(url2)
db_configure(staff, finance) # see below
... etc
# in another file
Session = scoped_session(sessionmaker())
def db_configure(staff, finance):
s = Session()
from db.finance import Employee, Customer, Invoice
for c in [
Employee,
Customer,
Invoice,
]:
s.bind_mapper(c, finance)
from db.staff import Project, Hour
for c in [
Project,
Hour,
]:
s.bind_mapper(c, staff)
s.close() # prevents leaking connections between sessions?
Więc wywołania create_engine () występują przy każdym żądaniu... Widzę, że jest to potrzebne, a Pula połączeń prawdopodobnie je buforuje i robi rzeczy rozsądnie.
Ale wywołanie sesji.bind_mapper () raz dla każdego tabeli, na każdego żądania? Wygląda na to, że musi być lepszy sposób.
Oczywiście, ponieważ pragnienie silnego bezpieczeństwa leży u podstaw tego wszystkiego, nie chcemy żadnej szansy, że połączenie ustanowione dla wysokiego bezpieczeństwa użytkownik nieumyślnie zostanie użyty w późniejszym żądaniu przez użytkownika o niskim poziomie bezpieczeństwa.
2 answers
Wiązanie obiektów globalnych (maperów, metadanych) do połączenia specyficznego dla użytkownika nie jest dobrym sposobem. Jak również za pomocą sesji scoped. Proponuję utworzyć nową sesję dla każdego żądania i skonfigurować ją tak, aby korzystała z połączeń specyficznych dla użytkownika. Poniższy przykład zakłada, że dla każdej bazy danych używa się oddzielnych obiektów metadanych:
binds = {}
finance_engine = create_engine(url1)
binds.update(dict.fromkeys(finance_metadata.sorted_tables, finance_engine))
# The following line is required when mappings to joint tables are used (e.g.
# in joint table inheritance) due to bug (or misfeature) in SQLAlchemy 0.5.4.
# This issue might be fixed in newer versions.
binds.update(dict.fromkeys([Employee, Customer, Invoice], finance_engine))
staff_engine = create_engine(url2)
binds.update(dict.fromkeys(staff_metadata.sorted_tables, staff_engine))
# See comment above.
binds.update(dict.fromkeys([Project, Hour], staff_engine))
session = sessionmaker(binds=binds)()
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-08 08:51:23
Chciałbym spojrzeć na połączenie pooling i zobaczyć, czy nie można znaleźć sposób, aby mieć jedną pulę na użytkownika.
Możesz dispose()
po wygaśnięciu sesji użytkownika
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-07 03:02:22