解析 User agent

應該是有可以解析 User Agent 以取得瀏覽器的版本、作業系統等資訊的函式庫吧,然後就找到了 python-user-agents

安裝

pip install ua-parser user-agents

使用很簡單,把 User agent 丟進 parse 去就可以了。

from user_agents import parse

x = parse('Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36')

print(str(x))
# PC / Linux / Chrome 78.0.3904

print(x.browser)
# Browser(family='Chrome', version=(78, 0, 3904), version_string='78.0.3904')

print(x.device)
# Device(family='Other', brand=None, model=None)

print(x.os)
# OperatingSystem(family='Linux', version=(), version_string='')

# 有這些方法/屬性可用:'_is_android_tablet', '_is_blackberry_touch_capable_device', 'browser', 'device', 'is_bot', 'is_email_client', 'is_mobile', 'is_pc', 'is_tablet', 'is_touch_capable', 'os', 'ua_string'

從 IP 取得 Country / City / 經緯度

使用的環境是 Ubuntu 16.04 + Django 2.2,先處理 geoip 資料庫。

sudo apt-get install geoipupdate

安裝完以後先填設定,編輯 /etc/GeoIP.conf ,這邊要注意 geoipupdate 版本,2.5 之前的話,AccountID 要改成 UserId,EditionIDs 要改成 ProductIds ,Ubuntu 16.04 用的是 2.5 之前的版本:

# The following AccountID and LicenseKey are required placeholders.
# For geoipupdate versions earlier than 2.5.0, use UserId here instead of AccountID.
# AccountID 0
UserId 0
LicenseKey 000000000000


# Include one or more of the following edition IDs:
# * GeoLite2-City - GeoLite 2 City
# * GeoLite2-Country - GeoLite2 Country
# For geoipupdate versions earlier than 2.5.0, use ProductIds here instead of EditionIDs.
# EditionIDs GeoLite2-City GeoLite2-Country
ProductIds GeoLite2-City GeoLite2-Country

執行 sudo -H geoipupdate,檔案會下載到 /var/lib/GeoIP

接著在 Django 環境下安裝 geoip2

pip install geoip2

然後先在 shell 裡試試看:python manage.py shell

from django.contrib.gis.geoip2 import GeoIP2
g = GeoIP2('/var/lib/GeoIP')  # 將路徑指過去
print(g.country('59.120.21.9'))
print(g.city('59.120.21.9'))
print(g.lat_lon('59.120.21.9'))

一般來說,用 city(“your_ip”) 就可以拿到足夠的資訊了

{'city': 'Taipei',
 'continent_code': 'AS',
 'continent_name': 'Asia',
 'country_code': 'TW',
 'country_name': 'Taiwan',
 'dma_code': None,
 'latitude': 25.0478,
 'longitude': 121.5318,
 'postal_code': None,
 'region': 'TPE',
 'time_zone': 'Asia/Taipei'}

參考資料

Using curl to upload file to Django without csrf_exempt

簡單的說就是利用 cookie 來取得必要的 CSRF TOKEN 資訊。

#!/bin/sh

ENDPOINT=http://localhost:8000/uploads/simple/
COOKIE_JAR=cookies.txt
# 是否要讓 curl 顯示詳細的資訊 (含 header 等)
# VERBOSE=-v
VERBOSE=

# Get
# 用 -c 存到 cookies.txt
curl \
    $VERBOSE \
    -c $COOKIE_JAR \
    $ENDPOINT

# Get CSRF token from cookies.txt
# cat $COOKIE_JAR
CSRFTOKEN=$(awk '/csrftoken/{print $7;}' $COOKIE_JAR)
echo "CSRFTOKEN=$CSRFTOKEN"

# POST 時,在標頭 X-CSRFToken 帶入剛剛找到的 CSRF token
curl \
    $VERBOSE \
    -X POST \
    -b $COOKIE_JAR \
    -H "X-CSRFToken: $CSRFTOKEN" \
    -F myfile=@image003.png \
    $ENDPOINT

jemalloc

jemalloc (github) 是效能聽說很好的 malloc library。

之前是在 為線上環境而最佳化的 Ruby:Fullstaq Ruby 看到的,看到的當下是想說,會不會也有人來編譯用 jemalloc 的 Python 版本,不過很遺憾的是沒有。後來找了 jemalloc 的資料以後,發現要替換其實不難。Ubuntu 14.04 是已經有包 libjemalloc 了,但版本較舊,是 3.5.1 版。

後來還是選擇自行編譯,然後使用 LD_PRELOAD 環境變數來替換 python 的 malloc library。

那要怎麼檢查 python 是不是真的有使用到呢?這可以使用 lsof 來檢查。(How to see the currently loaded shared objects in Linux?)

lsof /usr/local/lib/libjemalloc.so.2

同款的還有 tcmalloc (Google 出的),有空也來測試看看。

P.S. ptmalloc 是 glibc 的 malloc。

簡略的看 Tastypie

跟 DRF 不一樣,Tastypie 以 ModelResource 為主,埋下 Resource 時,就是完整的 LIST/CREATE/UPDATE/DELETE。

Resource 的 model 不一定要是 django model,也可以是自訂的 resource:https://django-tastypie.readthedocs.io/en/latest/non_orm_data_sources.html

  • Authentication 蠻多的,Basic/ApiKey/Session/Digest/OAuth/Multi 都有,OAuth 有內建。
  • Authorization 是指定允許的動作,像是 read_list / read_detail / create_list …. 等等。
  • Serializer 跟 DRF 有點不一樣,這邊僅指輸出的格式,DRF 主要是指輸出哪些欄位。
  • Throttling 可指定一秒內能呼叫的次數
  • 有支援 Paginator 翻頁。
  • 支援 GeoDjango!! 這倒是很方便,GeoDjango 看來是值得花時間來研究的。

缺點是,github 的活躍度不是太高,上次的更新是4個月前 (2018/9)。

跟 DRF 的比較可以參考這篇:https://stackshare.io/stackups/django-rest-framework-vs-tastypie

celery 無法 inspect

環境:

  • Celery 4.2.1
  • Broker backend: RabbitMQ
  • Result backend: Redis

想使用 celery inspect 來查 memory leak 問題,但是 celery inspect 時,都會出現 Error: No nodes replied within time constraint. 的訊息。

察看 log 以後,確定當 celery log 出現 warning,connection reset by peer 時,celery status 就會出現 “Error: No nodes replied within time constraint.” ,在這個同時,rabbitmq server log 也會出現 Missed heartbeats from client, timeout: 20s 的 ERROR ,有看過以下 issues,但沒幫助

後來找到這篇 rabbitmq报Heartbeat missing with heartbeat = 60 seconds ,參考內容把 celery 的 broker_heartbeat 設定改為 0,避免 RabbitMQ 做 heartbeat 的檢查。看來就解決了。

原理的解說可以參考這篇:Detecting Dead TCP Connections with Heartbeats and TCP Keepalives 大致上是說,RabbitMQ 有連線 heartbeat,當 client 沒回應的時候,RabbitMQ 會主動斷掉。而這個 heartbeat 是可以透過 client 在初始化連線時去設定的,所以調整 celery 設定,請 RabbitMQ 不要做 heartbeat 檢查就可以了。

Django jsonfield

Django 有提供 jsonfield,但只能用在 postgresql 上,有另外一個專案 django-mysql 提供了可以用在 MySQL 上的 jsonfield。是的,就目前來說,並沒有一個通用的 jsonfield。惟一能找到的,就這個 django-jsonfielddjango-jsonfield 的網站上也有特別提到這件事情,並且說 django-jsonfield 只繼續維護,不再開發,因此不建議使用這個專案。但是就跨資料庫來說,目前好像也就這個專案可用。

我有想過要依照 DATABASE 設定來區分要使用哪個 jsonfield,但仔細想想,這最大的問題可能會出在 Migration,因為 Migration 裡會直接引用 jsonfield 。migration 裡沒辦法讀取到 django settings ,也就沒辦法做到動態的處理。不過,可能還是要試試看才知道行不行。

s3proxy

網址:https://github.com/gaul/s3proxy
可以在本機端測試 S3,而不需要去開 S3 bucket 跟弄 AWS credential ,s3proxy 提供了跟 S3 一樣的 API 介面。

boto3 可以用,用法參考這邊:https://github.com/gaul/s3proxy/wiki/Client-compatibility-list

# Python3 + boto3 example
session = boto3.session.Session(aws_access_key_id='identity',
                                aws_secret_access_key='credential')
config = boto3.session.Config(s3={'addressing_style': 'path'})
# low level S3 client
client = session.client('s3', endpoint_url='http://localhost:60080',
                        config=config)
# S3 resource object
resource = session.resource('s3', endpoint_url='http://localhost:60080',
                            config=config)

用 docker 快速啟動

mkdir -p /tmp/data
docker run --publish 60080:80 -v /tmp/data:/data --env S3PROXY_AUTHORIZATION=none andrewgaul/s3proxy

django-storage 的話,應該是要改設定裡的 AWS_S3_ENDPOINT_URL

django stronghold

網址:https://github.com/mgrouchy/django-stronghold

這個 package 蠻好玩的,django 預設的 view 都是 public 的,得加上 LoginRequired decorator 或繼承 LoginRequiredMixin 才能限制只有使用者能用,但 stronghold 是反過來,在 middleware 加上 LoginRequiredMiddleware,強制所有的 view 都是 LoginRequired,只有加上 public decorator 或繼承 StrongholdPublicMixin 的才是 public。

對一個都需要驗證後才能使用的系統,這倒是方便許多。

gspread

因為想操作 google 試算表,找到 gspread 這個專案。

官方網頁的設定方式語焉不詳:https://gspread.readthedocs.io/en/latest/oauth2.html

所以我設定方式後來是參考這篇:https://sites.google.com/site/zsgititit/home/python-cheng-shi-she-ji/shi-yongpython-shang-chuan-zi-liao-daogoogle-shi-suan-biao

Google API console 那邊會比較讓人搞不清楚怎麼弄,大致紀錄一下:

  1. 建立 service account,角色選「編輯者」
  2. 建立金鑰 (選 JSON),然後下載
  3. 要編輯的 google spreadsheet 那邊要設定「共用」,email 填 json 檔案裡的 client email

操作 google 試算表的方式可以參考官方文件:https://gspread.readthedocs.io/en/latest/user-guide.html#getting-a-cell-value

其他參考文章: