First release of the project

This commit is contained in:
hibobmaster 2023-08-20 14:09:23 +08:00
parent 1b9f25e22d
commit 5982bc0777
Signed by: bobmaster
SSH key fingerprint: SHA256:5ZYgd8fg+PcNZNy4SzcSKu5JtqZyBF8kUhY7/k2viDk
13 changed files with 414 additions and 1 deletions

76
.github/workflows/docker-release.yml vendored Normal file
View file

@ -0,0 +1,76 @@
name: Publish Docker image
on:
workflow_dispatch:
release:
types: [published]
jobs:
push_to_registry:
name: Push Docker image to registry
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Check out the repo
uses: actions/checkout@v3
- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Docker metadata
id: meta
uses: docker/metadata-action@v4
with:
images: hibobmaster/iplookupserver
tags: |
type=raw,value=latest
type=ref,event=tag
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Build and push Docker image(dockerhub)
uses: docker/build-push-action@v4
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Docker metadata(ghcr)
id: meta2
uses: docker/metadata-action@v4
with:
images: ghcr.io/hibobmaster/iplookupserver
tags: |
type=raw,value=latest
type=sha,format=long
- name: Build and push Docker image(ghcr)
uses: docker/build-push-action@v4
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta2.outputs.tags }}
labels: ${{ steps.meta2.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

160
.gitignore vendored Normal file
View file

@ -0,0 +1,160 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

15
Dockerfile Normal file
View file

@ -0,0 +1,15 @@
FROM python:3.11-alpine as base
FROM base as pybuilder
# RUN sed -i 's|v3\.\d*|edge|' /etc/apk/repositories
COPY requirements.txt /requirements.txt
RUN pip install --user -r /requirements.txt && rm /requirements.txt
FROM base as runner
COPY --from=pybuilder /root/.local /usr/local
COPY . /app
FROM runner
WORKDIR /app
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--proxy-headers"]

View file

@ -1 +1,18 @@
# ip-info-lookup-server
# ip-info-lookup-server
仿照 ip.p3terx.com 样式构建的ip信息查询服务
## Usage
GET / get ip info from your ip address
POST / get json-formatted ip info from your ip address
GET /ip/{ip} get json-formatted ip info
POST /ip/{ip} get json-formatted ip info
Example:
```bash
curl ip.qqs.tw
curl ip.qqs.tw/ip/114.114.114.114
```

7
compose.yaml Normal file
View file

@ -0,0 +1,7 @@
services:
app:
image: hibobmaster/iplookupserver:test
ports:
- "127.0.0.1:8000:8000"
restart: no
# restart:unless-stopped

36
main.py Normal file
View file

@ -0,0 +1,36 @@
from fastapi import FastAPI, Request
from fastapi.responses import PlainTextResponse, JSONResponse
from mmdb_func import ip_validator
from results import struct_ip_info_str, struct_ip_info
app = FastAPI()
@app.get("/", response_class=PlainTextResponse)
def rootg(request: Request):
return (
struct_ip_info_str(request.client.host)
+ "\n\n"
+ request.headers.get("User-Agent")
)
@app.post("/")
def rootp(request: Request):
return JSONResponse(content=struct_ip_info(request.client.host), status_code=200)
@app.get("/ip/{ip}", response_class=PlainTextResponse)
def ipg(ip: str, request: Request):
if ip_validator(ip):
return struct_ip_info_str(ip) + "\n" + request.headers.get("User-Agent")
else:
return {"error": "invalid ip address"}
@app.post("/ip/{ip}")
def ipp(ip: str):
if ip_validator(ip):
return JSONResponse(content=struct_ip_info(ip), status_code=200)
else:
return JSONResponse(content={"error": "invalid ip address"}, status_code=400)

BIN
mmdb/GeoLite2-ASN.mmdb Normal file

Binary file not shown.

BIN
mmdb/GeoLite2-Country.mmdb Normal file

Binary file not shown.

40
mmdb_func.py Normal file
View file

@ -0,0 +1,40 @@
import ipaddress
import geoip2.database
import geoip2.errors
class MMDB:
def __init__(self, asn_db_path: str, country_db_path: str):
self.asn_reader = geoip2.database.Reader(asn_db_path)
self.country_reader = geoip2.database.Reader(country_db_path)
def get_asn_num(self, ip: str) -> int:
try:
return self.asn_reader.asn(ip).autonomous_system_number
except geoip2.errors.AddressNotFoundError:
return "N/A"
def get_asn_org(self, ip: str) -> str:
try:
return self.asn_reader.asn(ip).autonomous_system_organization
except geoip2.errors.AddressNotFoundError:
return "N/A"
def get_country_iso(self, ip: str) -> str:
try:
return self.country_reader.country(ip).country.iso_code
except geoip2.errors.AddressNotFoundError:
return "N/A"
def get_country_name(self, ip: str) -> str:
try:
return self.country_reader.country(ip).country.name
except geoip2.errors.AddressNotFoundError:
return "N/A"
def ip_validator(ip: str):
try:
return True if ipaddress.ip_address(ip) else False
except ValueError:
return False

3
requirements.txt Normal file
View file

@ -0,0 +1,3 @@
fastapi
uvicorn
geoip2

29
results.py Normal file
View file

@ -0,0 +1,29 @@
from mmdb_func import MMDB
mmdb = MMDB("mmdb/GeoLite2-ASN.mmdb", "mmdb/GeoLite2-Country.mmdb")
def struct_ip_info(ip: str) -> dict:
return {
"ip": ip,
"asn": {
"num": mmdb.get_asn_num(ip),
"org": mmdb.get_asn_org(ip),
},
"country": {
"iso": mmdb.get_country_iso(ip),
"name": mmdb.get_country_name(ip),
},
}
def struct_ip_info_str(ip: str) -> str:
return (
f"{ip}\n"
+ f"{mmdb.get_country_iso(ip)} / {mmdb.get_country_name(ip)}\n"
+ (
f"AS{mmdb.get_asn_num(ip)} / {mmdb.get_asn_org(ip)}\n"
if mmdb.get_asn_num(ip) != "N/A"
else "N/A"
)
)

11
tests/api.http Normal file
View file

@ -0,0 +1,11 @@
# ("/")
###
GET http://127.0.0.1:8000/
###
POST http://127.0.0.1:8000/
# ("/ip")
###
GET http://127.0.0.1:8000/ip/114.114.114.114
###
POST http://127.0.0.1:8000/ip/119.29.29.29

19
tests/test_mmdb.py Normal file
View file

@ -0,0 +1,19 @@
from mmdb_func import MMDB, ip_validator
mmdb = MMDB("mmdb/GeoLite2-ASN.mmdb", "mmdb/GeoLite2-Country.mmdb")
real_ip = "114.114.114.114"
bad_ip = "aaa.xxx.ccc.ddd"
class TestClass:
def test_ip_validator(self):
assert ip_validator(real_ip) is True
assert ip_validator(bad_ip) is False
def test_get_asn(self):
assert mmdb.get_asn_num(real_ip) == 174
assert mmdb.get_asn_org(real_ip) == "COGENT-174"
def test_get_country(self):
assert mmdb.get_country_iso(real_ip) == "CN"
assert mmdb.get_country_name(real_ip) == "China"