Skip to content

Commit

Permalink
-l IP地址 -p 端口
Browse files Browse the repository at this point in the history
提供一个http服务,响应返回请求的IP地址,配合ddns使用
  • Loading branch information
yi-shiyu committed Apr 16, 2024
1 parent 489a636 commit c1b8a98
Show file tree
Hide file tree
Showing 5 changed files with 192 additions and 0 deletions.
36 changes: 36 additions & 0 deletions .github/workflows/publish-docker-images.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Publish Version
on:
push:
tags:
- 'v*'

env:
DOCKERHUB_REPO: evlan/http_echo_ip
APP_VERSION: ${{ github.ref_name }}

jobs:
pulish-docker-images:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up QEMU
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Build and push
uses: docker/build-push-action@v5
with:
push: true
platforms: linux/amd64,linux/arm64
tags: ${{ env.DOCKERHUB_REPO }}:latest,${{ env.DOCKERHUB_REPO }}:${{ env.APP_VERSION }}

37 changes: 37 additions & 0 deletions .github/workflows/publish-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Release

on:
push:
tags:
- 'v*'

jobs:
create-release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: taiki-e/create-gh-release-action@v1
with:
token: ${{ secrets.RELEASE_TOKEN }}

upload-assets:
needs: create-release
strategy:
matrix:
include:
- target: x86_64-unknown-linux-gnu
os: ubuntu-latest
- target: x86_64-apple-darwin
os: macos-latest
- target: x86_64-pc-windows-msvc
os: windows-latest
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: taiki-e/upload-rust-binary-action@v1
with:
bin: http_echo_ip
target: ${{ matrix.target }}
tar: unix
zip: windows
token: ${{ secrets.RELEASE_TOKEN }}
12 changes: 12 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "http_echo_ip"
version = "1.0.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
chrono = "0.4.37"
clap = { version = "4.5.4", features = ["derive"] }
regex = "1.10.4"
tokio = { version = "1.37.0", features = ["io-util", "net", "rt", "rt-multi-thread", "macros"] }
15 changes: 15 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
FROM rust:alpine AS builder

WORKDIR /usr/src/myapp

COPY . .

RUN apk update && apk add musl musl-dev && cargo build --release

FROM alpine:latest

WORKDIR /usr/src/myapp

COPY --from=builder /usr/src/myapp/target/release/http_echo_ip .

ENTRYPOINT ["./http_echo_ip"]
92 changes: 92 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
use std::collections::HashMap;
use chrono::{FixedOffset, Local};
use clap::Parser;
use regex::Regex;
use std::error::Error;
use std::sync::{Arc, Mutex};
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::net::{TcpListener, TcpStream};

#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Args {
/// 监听地址
#[arg(short, long, default_value = "127.0.0.1")]
listen: String,

/// 监听端口号
#[arg(short, long, default_value_t = 80)]
port: usize,
}

impl Args {
fn to_string(&self) -> String {
format!("{}:{}", self.listen, self.port)
}
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let args = Args::parse();
let server = TcpListener::bind(args.to_string()).await?;
println!("{}Listening on: {}", get_time(), args.to_string());

let cache_times = Arc::new(Mutex::new(HashMap::new()));

//正则从header读 nginx转发带的
let forwarded_for_regex = Regex::new(r"X-Forwarded-For:\s*(?P<ip>(?:\d{1,3}\.){3}\d{1,3})").unwrap();
let real_ip_regex = Regex::new(r"X-Real-IP:\s*(?P<ip>(?:\d{1,3}\.){3}\d{1,3})").unwrap();
let remote_host_regex = Regex::new(r"REMOTE-HOST:\s*(?P<ip>(?:\d{1,3}\.){3}\d{1,3})").unwrap();
let regexes = Arc::new(Vec::from([forwarded_for_regex, real_ip_regex, remote_host_regex]));

loop {
let regexes = Arc::clone(&regexes);
let cache_times = Arc::clone(&cache_times);
let (stream, _) = server.accept().await?;
tokio::spawn(async move {
if let Err(e) = process(stream, regexes, cache_times).await {
println!("failed to process connection; error = {}", e);
}
});
}
}

async fn process(mut stream: TcpStream, regexes: Arc<Vec<Regex>>, cache_times: Arc<Mutex<HashMap<String, usize>>>) -> Result<(), Box<dyn Error>> {
//直接读链接的地址
let mut ip = stream.peer_addr()?.ip().to_string();
let mut http_request = BufReader::new(&mut stream).lines();

//接收数据从header再读一遍
while let Some(line) = http_request.next_line().await? {
if line.len() == 0 {
break;
}
for regex in regexes.iter() {
if let Some(capture) = regex.captures(&line) {
if let Some(v) = capture.name("ip") {
ip = v.as_str().to_string();
}
}
}
}
stream.write_all(format!("HTTP/1.1 200 OK\r\nContent-Length: {}\r\nContent-Type: text/html; charset=utf-8\r\n\r\n{}\r\n", ip.len(), ip).as_bytes()).await?;
let mut map = cache_times.lock().unwrap();
let mut times = 1;
match map.get_mut(&ip) {
None => {
map.insert(ip.clone(), 1);
}
Some(v) => {
*v += 1;
times = *v;
}
}
println!("{}Request from: {:<15}({})", get_time(), ip, times);
Ok(())
}

fn get_time() -> String {
let offset = FixedOffset::east_opt(8 * 60 * 60).unwrap();
let shanghai_time = Local::now().with_timezone(&offset);
shanghai_time.format("[%Y-%m-%d %H:%M:%S%.3f]").to_string()
}

0 comments on commit c1b8a98

Please sign in to comment.