Skip to content

Commit

Permalink
♻️ refactor: Refactoring to Go for performance and fix buffer overflo…
Browse files Browse the repository at this point in the history
…w erros
  • Loading branch information
kms0219kms committed Sep 21, 2024
1 parent 6f3e071 commit a89357f
Show file tree
Hide file tree
Showing 13 changed files with 283 additions and 124 deletions.
43 changes: 43 additions & 0 deletions .github/workflows/deploy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Deploy to GitHub Container Registry

on:
push:
branches:
- main
workflow_dispatch:
workflow_call:

env:
IMAGE_NAME: youtubei-proxy

jobs:
push:
runs-on: ubuntu-latest
permissions:
packages: write
contents: read
steps:
- 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

- id: dockertag
name: Get Docker tag
uses: ASzc/change-string-case-action@v6
with:
string: ghcr.io/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:latest

- name: Log in to registry
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin

- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.dockertag.outputs.lowercase }}
16 changes: 15 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
{
"deno.enable": true
"editor.formatOnSave": true,

"[go]": {
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit"
}
},

"[go.mod]": {
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit"
}
}
}
16 changes: 16 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
FROM golang:1.23.1-alpine
WORKDIR /usr/src/app

# Effectively tracks changes within your go.mod file
COPY src/go.mod .
COPY src/go.sum .
RUN go mod download

# Copies your source code into the app directory
COPY src/main.go .
RUN go build -o proxy

# Run the app
EXPOSE 8123/tcp

CMD ["./proxy"]
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Youtubei (Innertube) Proxy written in Go

To install dependencies:

```bash
go install
```

To run:

```bash
go run main.go
```

This project was created using go v1.23.1.
15 changes: 15 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
version: "3.8"

services:
youtubei-proxy:
container_name: youtubei-proxy
image: ghcr.io/waktaplay/youtubei-proxy:latest
ports:
- "8123:8123"
networks:
- nginx-bridge
restart: always

networks:
nginx-bridge:
external: true
2 changes: 0 additions & 2 deletions index.ts

This file was deleted.

12 changes: 0 additions & 12 deletions makefile

This file was deleted.

27 changes: 27 additions & 0 deletions src/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# This file was created with ignofier

# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib

# Test binary, built with `go test -c`
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# Dependency directories (remove the comment below to include it)
# vendor/

# Go workspace file
go.work
go.work.sum

# env file
.env
8 changes: 8 additions & 0 deletions src/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module youtubei_proxy

go 1.23.1

require (
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be
golang.org/x/net v0.29.0
)
4 changes: 4 additions & 0 deletions src/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be h1:J5BL2kskAlV9ckgEsNQXscjIaLiOYiZ75d4e94E6dcQ=
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be/go.mod h1:mk5IQ+Y0ZeO87b858TlA645sVcEcbiX6YqP98kt+7+w=
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
140 changes: 140 additions & 0 deletions src/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package main

import (
"fmt"
"io"
"log"
"os"
"time"

"net/http"
"net/url"

"encoding/json"
"github.com/common-nighthawk/go-figure"
"golang.org/x/net/proxy"
)

const (
PORT = 8123
CHUNK_SIZE = 10 * 1024 * 1024 // 10MB
)

func copyHeader(headerName string, to http.Header, from http.Header) {
hdrVal := from.Get(headerName)
if hdrVal != "" {
to.Set(headerName, hdrVal)
}
}

func handler(w http.ResponseWriter, req *http.Request) {
method := req.Method
headers := req.Header
urlStr := req.URL.String()

// CORS preflight 처리
if method == http.MethodOptions {
w.Header().Set("Access-Control-Allow-Origin", headers.Get("Origin"))
w.Header().Set("Access-Control-Allow-Methods", "*")
w.Header().Set("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization")
w.WriteHeader(http.StatusOK)
return
}

urlObj, err := url.Parse(urlStr)
if err != nil {
http.Error(w, "Invalid URL", http.StatusBadRequest)
return
}

if !urlObj.Query().Has("__host") {
http.Error(w, "Request is formatted incorrectly. Please include __host in the query string.", http.StatusBadRequest)
return
}

urlObj.Scheme = "https"
urlObj.Host = urlObj.Query().Get("__host")
query := urlObj.Query()
query.Del("__host")
urlObj.RawQuery = query.Encode()

// 헤더 복사
requestHeaders := make(http.Header)
if headersStr := urlObj.Query().Get("__headers"); headersStr != "" {
json.Unmarshal([]byte(headersStr), &requestHeaders)
}
copyHeader("User-Agent", requestHeaders, headers)
query.Del("__headers")
urlObj.RawQuery = query.Encode()

proxyURL := os.Getenv("PROXY")
client := &http.Client{
Transport: &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
IdleConnTimeout: 90 * time.Second,
},
}

if proxyURL != "" {
proxyURLParsed, err := url.Parse(proxyURL)
if err == nil {
dialer, err := proxy.FromURL(proxyURLParsed, proxy.Direct)
if err == nil {
transport := &http.Transport{Dial: dialer.Dial}
client.Transport = transport
}
}
}

// 청크 단위로 요청
chunkStart := 0
for {
chunkEnd := chunkStart + CHUNK_SIZE - 1
if chunkEnd < 0 {
break
}

proxyReq, err := http.NewRequest(method, urlObj.String(), nil)
if err != nil {
log.Fatal("Failed to create request", err)
http.Error(w, "Failed to create request", http.StatusInternalServerError)
return
}

// Range 헤더 설정
proxyReq.Header = requestHeaders
proxyReq.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", chunkStart, chunkEnd))

fetchRes, err := client.Do(proxyReq)
if err != nil {
log.Fatal("Failed to fetch response", err)
http.Error(w, "Failed to fetch response", http.StatusInternalServerError)
return
}
defer fetchRes.Body.Close()

// 응답 처리
if fetchRes.StatusCode != http.StatusPartialContent {
break
}

// 응답 스트림을 클라이언트에 전달
if _, err := io.Copy(w, fetchRes.Body); err != nil {
log.Printf("Error writing response: %v", err)
return
}

chunkStart += CHUNK_SIZE // 다음 청크로 이동
}
}

func main() {
fig := figure.NewFigure("Innertube Proxy", "doom", true)
fmt.Println(fig.String())
fmt.Println("----------------------------------------------------------------")

http.HandleFunc("/", handler)
log.Printf("[INFO] Server is running on port %d.\n", PORT)
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", PORT), nil))
}
Loading

0 comments on commit a89357f

Please sign in to comment.