from flask import Flask, request, Response
import requests
import logging
from logging.handlers import RotatingFileHandler
app = Flask(__name__)
# netsh advfirewall firewall add rule name="Allow [EXTERNAL_PORT]" dir=in action=allow protocol=TCP localport=[EXTERNAL_PORT]
# 포워딩할 대상 URL (내부 HTTP 서버)
INTERNAL_PORT = 54321 # 내부 서버 포트 (예시)
TARGET = f"http://127.0.0.1:{INTERNAL_PORT}"
# 로그 파일 설정: server.log 파일에 최대 10,000바이트까지 저장하며, 1개의 백업 파일 생성
handler = RotatingFileHandler('server.log', maxBytes=10000, backupCount=1, encoding='utf-8')
handler.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s %(levelname)s: %(message)s')
handler.setFormatter(formatter)
app.logger.addHandler(handler)
app.logger.setLevel(logging.INFO)
# 모든 경로와 HTTP 메서드를 처리하도록 라우팅
@app.route('/', defaults={'path': ''}, methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])
@app.route('/', methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])
def proxy(path):
target_url = f"{TARGET}/{path}"
app.logger.info(f"Request: {request.method} {request.url} -> Forwarding to: {target_url}")
headers = {key: value for key, value in request.headers if key.lower() != 'host'}
# 대상 서버에 요청 전송
resp = requests.request(
method=request.method,
url=target_url,
headers=headers,
params=request.args,
data=request.get_data(),
cookies=request.cookies,
allow_redirects=False
)
app.logger.info(f"Response: Status code {resp.status_code} from {target_url}")
# 대상 서버의 응답을 그대로 클라이언트에 전달
response = Response(resp.content, resp.status_code)
for key, value in resp.headers.items():
response.headers[key] = value
return response
if __name__ == '__main__':
# Let's Encrypt 인증서 사용 (인증서 파일 경로는 마스킹 처리됨)
ssl_context = ("[인증서 경로]/fullchain.pem", "[인증서 경로]/privkey.pem")
EXTERNAL_PORT = 12345 # 외부에 노출될 포트 (예시)
app.logger.info(f"Starting HTTPS reverse proxy server on port {EXTERNAL_PORT} with Let's Encrypt certificate")
app.run(host='0.0.0.0', port=EXTERNAL_PORT, ssl_context=ssl_context)
2025년 3월 15일 토요일
Python 으로 작성한 리버스 프록시 코드 (Flask 사용 내부 테스트용)
파이썬으로 작성한 리버스프록시 코드.
안드로이드 앱이 외부 접속을 할 때 https 가 아니면 안되는 경우가 있어서 제작함.
라벨:
dev,
flask,
python,
reverseproxy
2025년 3월 5일 수요일
Using OpenAI's ChatGPT with Unity C# via API // Unity로 ChatGPT 챗봇 만들기 (API 이용)
# Unity C# 으로 구동하는 챗봇 코드
5. 유니티로 돌아와서 플레이 해본다. log 창에 표시가 된다.
내가 쓸려고 간단히 만든 것을 기록용으로 공개한다.
아래 내용을 유니티에 붙여 넣는다.
1. 빈 gameobject 를 하나 생성한다.
2. new component 로 ChatbotManager 라는 스크립트를 생성해서 1에 붙인다.
3. 코드에디터로 아래 내용을 복사해서 2의 파일에 붙이고 저장한다.
4. 유니티 내 StreamingAssets 경로에 auth.json 을 생성한다.
/// StreamingAssets 폴더의 auth.json 파일에서 API 키를 로드합니다.
/// 예시 파일 형식: { "api_key": "your_api_key_here" }
# 사용 법
StartNewConversation() : 새로운 대화 스레드 시작
SendUserMessage() : 내 대사 전달
# 메인 코드
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
[System.Serializable]
public class ChatMessage {
public string role;
public string content;
}
[System.Serializable]
public class ChatCompletionRequest {
public string model;
public List messages;
public int max_tokens;
public float temperature;
}
[System.Serializable]
public class ChatCompletionChoice {
public ChatMessage message;
public string finish_reason;
public int index;
}
[System.Serializable]
public class ChatbotResponse {
public string id;
public string @object;
public int created;
public string model;
public List choices;
}
[System.Serializable]
public class AuthData {
public string api_key;
}
public class ChatbotManager : MonoBehaviour
{
// Inspector에 노출되지 않도록 private const로 선언하여 잘못된 값이 덮어씌워지지 않게 합니다.
private const string apiUrl = "https://api.openai.com/v1/chat/completions";
public string model = "gpt-4o-mini";
// public string model = "gpt-4o-mini"; // "o3-mini"; //"gpt-3.5-turbo"
// 응답 속도 개선을 위한 추가 파라미터
[Tooltip("응답 생성 시 최대 토큰 수를 제한합니다. 낮을수록 응답이 빨라집니다.")]
public int maxTokens = 150;
[Tooltip("응답의 창의성을 조절합니다. 0에 가까울수록 결정적이고 빠른 응답이 생성됩니다.")]
[Range(0f, 1f)]
public float temperature = 0.3f;
private string apiKey;
private List messages = new List();
[TextArea(3, 10)]
[Tooltip("챗봇에게 전달할 초기 시스템 인스트럭션입니다. 챗봇의 역할과 행동 방식을 정의합니다.")]
public string initialInstruction = "안녕하세요, 챗봇과 대화를 시작합니다. 아래 instruction을 참고하세요.";
[Tooltip("대화 기록에 유지할 최대 메시지 수입니다. 너무 많은 메시지는 응답 속도를 늦출 수 있습니다.")]
public int maxMessageHistory = 10;
[Tooltip("대화 기록을 저장하는 배열입니다. 인스펙터에서 확인할 수 있습니다.")]
[SerializeField] private ChatMessage[] messageHistory;
void Start()
{
LoadApiKey();
StartNewConversation();
}
///
/// StreamingAssets 폴더의 auth.json 파일에서 API 키를 로드합니다.
/// 예시 파일 형식: { "api_key": "your_api_key_here" }
///
private void LoadApiKey()
{
string authFilePath = System.IO.Path.Combine(Application.streamingAssetsPath, "auth.json");
if (System.IO.File.Exists(authFilePath))
{
try
{
string jsonContent = System.IO.File.ReadAllText(authFilePath);
Debug.Log("로드된 auth.json 내용: " + jsonContent);
if (!jsonContent.Trim().StartsWith("{") || !jsonContent.Trim().EndsWith("}"))
{
Debug.LogError("auth.json 파일이 올바른 JSON 형식이 아닙니다. 올바른 형식: {\"api_key\":\"your_api_key_here\"}");
return;
}
AuthData authData = JsonUtility.FromJson(jsonContent);
if (authData != null && !string.IsNullOrEmpty(authData.api_key))
{
apiKey = authData.api_key;
Debug.Log("API 키가 성공적으로 로드되었습니다.");
}
else
{
Debug.LogError("auth.json 파일에서 API 키를 찾을 수 없습니다.");
}
}
catch (System.Exception e)
{
Debug.LogError("auth.json 파일 파싱 중 오류 발생: " + e.Message);
}
}
else
{
Debug.LogError("auth.json 파일을 찾을 수 없습니다. 경로: " + authFilePath);
}
}
///
/// 새 대화를 시작합니다. 기존 대화 기록을 초기화하고 초기 instruction 메시지를 추가합니다.
///
public void StartNewConversation()
{
messages.Clear();
ChatMessage systemMessage = new ChatMessage { role = "system", content = initialInstruction };
messages.Add(systemMessage);
// 메시지 배열 업데이트
messageHistory = messages.ToArray();
Debug.Log("새 대화가 시작되었습니다. 초기 instruction이 추가되었습니다.");
}
///
/// 사용자 메시지를 추가하고 API로 전송합니다.
///
/// 사용자 입력 메시지
public void SendUserMessage(string userMessageContent)
{
ChatMessage userMessage = new ChatMessage { role = "user", content = userMessageContent };
messages.Add(userMessage);
// 메시지 수가 제한을 초과하면 오래된 메시지 제거 (시스템 메시지는 유지)
TrimMessageHistory();
StartCoroutine(SendRequestCoroutine());
}
///
/// 대화 기록이 너무 길어지지 않도록 오래된 메시지를 제거합니다.
///
private void TrimMessageHistory()
{
// 시스템 메시지를 제외한 메시지 수가 maxMessageHistory를 초과하면 오래된 메시지 제거
if (messages.Count > maxMessageHistory + 1) // +1은 시스템 메시지 때문
{
// 시스템 메시지는 항상 인덱스 0에 있다고 가정
int excessMessages = messages.Count - maxMessageHistory - 1;
messages.RemoveRange(1, excessMessages); // 시스템 메시지 다음부터 제거
}
// 메시지 리스트를 배열로 변환하여 인스펙터에서 확인할 수 있게 함
messageHistory = messages.ToArray();
}
///
/// 대화 전체를 JSON으로 직렬화하여 API에 POST 요청을 보내고, 응답을 받아 처리합니다.
///
IEnumerator SendRequestCoroutine()
{
if (string.IsNullOrEmpty(apiKey))
{
Debug.LogError("API 키가 설정되지 않았습니다. API 요청을 보낼 수 없습니다.");
yield break;
}
ChatCompletionRequest requestObj = new ChatCompletionRequest
{
model = model,
messages = messages,
max_tokens = maxTokens,
temperature = temperature
};
string jsonData = JsonUtility.ToJson(requestObj);
Debug.Log("요청 JSON: " + jsonData);
// UnityWebRequest 생성 시 POST 메서드를 직접 지정합니다.
UnityWebRequest request = new UnityWebRequest(apiUrl, "POST");
byte[] bodyRaw = System.Text.Encoding.UTF8.GetBytes(jsonData);
request.uploadHandler = new UploadHandlerRaw(bodyRaw);
request.downloadHandler = new DownloadHandlerBuffer();
request.SetRequestHeader("Content-Type", "application/json");
// API 키에서 불필요한 문자를 제거합니다.
string sanitizedApiKey = apiKey.Trim().Replace("\n", "").Replace("\r", "").Replace("\t", "").Replace("\"", "");
request.SetRequestHeader("Authorization", "Bearer " + sanitizedApiKey);
Debug.Log("API URL: " + apiUrl);
Debug.Log("요청 메서드: " + request.method);
yield return request.SendWebRequest();
Debug.Log("응답 상태 코드: " + request.responseCode);
if (request.result == UnityWebRequest.Result.Success)
{
string responseText = request.downloadHandler.text;
Debug.Log("응답 JSON: " + responseText);
ChatbotResponse response = JsonUtility.FromJson(responseText);
if (response != null && response.choices != null && response.choices.Count > 0)
{
ChatMessage assistantMessage = response.choices[0].message;
messages.Add(assistantMessage);
// 메시지 배열 업데이트
messageHistory = messages.ToArray();
Debug.Log("Assistant: " + assistantMessage.content);
}
}
else
{
Debug.LogError("API 요청 오류: " + request.error);
Debug.LogError("응답 내용: " + request.downloadHandler.text);
}
}
}
내가 도움 받은 만큼 다른 누군가에게 도움이 되기를.
끝.
2022년 1월 24일 월요일
Python 으로 텍스트 인코딩 정보 확인하기 (Python3, 파이썬3)
윈도우에서 파이썬을 사용하다보면 텍스트 파일 (csv) 등을 읽을 때 인코딩 이슈가 자주 있다.
판다스로 csv 파일을 읽어올 때 인코딩 타입을 지정해줘야 하는데,
가끔 쓰는 툴 마다 인코딩이 달라서 에러가 발생한다.
예를 들어 MS Office Excel 은 euc-kr (cp949 로 읽을 수 있다.) 인데, 파이썬은 UTF-8 이고, 언리얼은 한글 포함된 문서 한정으로 UTF-16 이고 아닐 때는 UTF-8 이다.
판다스로 읽을 때 아래와 같이 인코딩 타입을 꼭 지정해줘야 하는데,
입력되는 소스의 인코딩이 제각각일 때는 약간 피곤하다.
Notepad++ 로 읽어서 인코딩 바꿔주는것은 어렵진 않지만 많은 양을 매번 바꿀 때는 번거롭다.
data = pd.read_csv('targetFileName', delimiter=',', encoding='cp949', engine='python') #인코딩타입은 꼭 바꿔주야 한다.
그래서 귀찮을 때 나를 도와주는 파이썬에게 부탁하여 해결해보았다.
아래 코드를 사용한다.
import chardet
#import sys
# 윈도우 환경에서 cp949 텍스트 인코딩 때문에 발생하는 문제를 우회하기 위한 코드이다.
# 아래는 만일을 위해 출력하는 것
#print(sys.stdin.encoding) #인풋 인코딩 타입
#print(sys.stdout.encoding) #아웃풋 인코딩 타입
file = ".\\확인할대상.csv" #csv 나 txt 등 텍스트 파일의 인코딩 타입을 확인하고 싶은 것의 경로를 입력한다.
def getDetectEncoding(file) :
try:
print("Fast Detect!!!")
with open(file, 'r') as f:
file_data = f.readlines() #텍스트로 읽어본다. 잘 읽히면 문제 없다. 보통 cp949 에러가 발생한다.
f.close()
#print(chardet.detect(file_data))
return chardet.detect(file_data[1])['encoding'] #0번째 줄은 보통 컬럼 헤더로 정보고 1번째 줄이 실제 내용이다. 빠른 처리를 위해 1줄만 읽어본다.
except:
print ("Except!!! not UTF-8 or 16. Retry Now... ")
with open(file, 'rb') as f:
file_data = f.read() #cp949 에러가 발생했을 때는 그냥 파일을 바이너리로 통채로 읽는다. 용량이 클 수록 느려진다. 하지만 쉽게 해결하는 방법
f.close()
#print(chardet.detect(file_data))
return chardet.detect(file_data)['encoding']
print(getDetectEncoding(file))
여기까지이고, 만일 판다스에서 인코딩을 자동으로 하게하려면 리턴되는 스트링을 encoiding에 바인딩 시키면 된다.
끝!!
파이썬 3.9 기준이다.
2020년 2월 7일 금요일
VBS 로 Excel Macro (Sub) 실행하기. Excel 백그라운드 매크로 실행
엑셀을 실행하지 않고 (백그라운드에서 실행하여) 매크로를 실행해주는 방법입니다.
VBS 를 사용합니다. (Visual Basic Script)
새 텍스트 파일을 만들어 확장자를 .vbs 로 변경해주시고 아래의 코드를 붙여넣어보세요.
엑셀 파일은 .xlsm 으로 미리 모듈 등으로 Sub문 매크로를 만들어두셔야 합니다.
VBS 파일의 경로는 xlsm과 같은 경로에 만듭니다.
이걸로 엑셀을 실행해서 작업을 하지 않아 더 빠르게 작업을 해둘 수 있어요.
적당히 응용해서 사용해보세요.
VBS 를 사용합니다. (Visual Basic Script)
새 텍스트 파일을 만들어 확장자를 .vbs 로 변경해주시고 아래의 코드를 붙여넣어보세요.
엑셀 파일은 .xlsm 으로 미리 모듈 등으로 Sub문 매크로를 만들어두셔야 합니다.
VBS 파일의 경로는 xlsm과 같은 경로에 만듭니다.
이걸로 엑셀을 실행해서 작업을 하지 않아 더 빠르게 작업을 해둘 수 있어요.
적당히 응용해서 사용해보세요.
Dim Excel, Path
Set Excel = WScript.createObject("Excel.Application")
Path = WScript.ScriptFullName ' 현재 실행하는 전체 파일이름 (경로+이름)
Path = Left(Path, InStrRev(Path, "\")) ' 이름제거후 경로만 추출
Excel.Workbooks.Open(Path&"엑셀파일이름.xlsm") '매크로 포함된 엑셀 파일 실행
'실행하고 싶은 Sub문 입력
'Sub문에 모든걸 해두고 그 함수를 호출하는 것이 순차적으로 처리되어 원하는 결과물이 나올 것입니다.
'순차적으로 Excel.Run 을 시키면 동시에 실행되서 이상한 결과가 나올 수 있어요.
'이 함수에서 처리가 끝난 후, 해당 Workbook 을 저장하기를 권합니다.
Excel.Run "Sub문 매크로 함수명"
' 파일 닫기
Excel.Quit
'변수 초기화
Set Excel = Nothing
Set Path = Nothing
2020년 2월 6일 목요일
Python 폴더 내 모든 wav 파일의 length를 가져와 모두 합치기 (duration 초 누산)
python 3.6.6 에서 확인했습니다.
wave 파일을 읽어서 frame수 / rate를 하면 길이가 sec으로 나옵니다.
wave 파일을 읽어서 frame수 / rate를 하면 길이가 sec으로 나옵니다.
#path_dir에 wav파일만 있다고 가정합니다.
#path_dir 에 있는 모든 파일을 읽어서 list에 넣고 length(duration)을 모두 더합니다.
import wave
import contextlib
import os
#import sys
path_dir = '드라이브:/경로/경로/' #경로 끝에 / 꼭 붙이기
file_list =os.listdir(path_dir) #경로 읽어 파일명 리스트 만들기
file_list.sort() #정렬
# 누적값 초기화
acc = 0
for i in file_list:
with contextlib.closing(wave.open(path_dir + i,'r')) as f:
frames = f.getnframes()
rate = f.getframerate()
duration = frames / float(rate)
#print(duration)
acc += duration #누적값
#결과 출력
print (acc)
2020년 1월 29일 수요일
Excel VBA 엑셀 파일 경로 값 가져오기 File Location
엑셀 파일 경로 값 가져오기
끝.
Function getCellValue(LOC_, strFile , strSheet , strPath )
'파일을 여는 것 따로 해줘야 하는데, 열었다 닫았다 prcess 과부하가 걸릴 수 있으므로 주의
Dim strPath, strFile, strSheet, strRng, strRef, Result As String
strPath = PathName_
strFile = FileName_
strSheet = SheetName_
'strRng = Range(LOC_).Value
strRng = LOC_
strRef = "'" & strPath & "[" & strFile & "]" & strSheet & "'!" & strRng
'Debug.Print (strRef)
getCellValue = Range(strRef).Value
End Function끝.
2019년 9월 11일 수요일
Excel Vba Sheet 내용을 Text 파일로 저장하기 (UTF-8)
시트의 내용을 텍스트 파일로 저장하기. (UTF-8 대응)
Sub SheetToText()
Dim streamWrite As New ADODB.Stream '// 쓰기 스트림 선언 Microsoft ADODB 6.1 (ActiveX) 참조 필수
Dim sText As Variant '// 파일 데이터 선언
'// 파일 쓰기
streamWrite.Type = adTypeText
streamWrite.Charset = "UTF-8"
streamWrite.Open
'// 첫줄에 넣을 메시지
streamWrite.WriteText "// 첫줄"
'//시트 데이터 읽는데 필요한 변수 선언
Dim rng As Range
Dim iRow As Long, iCol As Integer
Dim sTxt As String, sPath As String, deLimiter As String
Set rng = ActiveSheet.UsedRange
deLimiter = ", " '// 구분자 "," 입력 바꿔도 됨
For iRow = 1 To rng.Rows.Count '// 1행부터 마지막 행까지
For iCol = 1 To rng.Columns.Count '// 1열부터 오른쪽 최대 열까지
sTxt = sTxt & ActiveSheet.Cells(iRow, iCol).Value & deLimiter
Next iCol
streamWrite.WriteText (Left(sTxt, Len(sTxt) - 1) & vbLf)
sTxt = vbNullString
Next iRow
'// 스트림의 마지막 표시
streamWrite.SetEOS
'// 세이브
Call streamWrite.SaveToFile(Application.ActiveWorkbook.Path + "\" + ActiveSheet.Name + ".txt", adSaveCreateOverWrite) '//저장할 경로와 파일 이름은 필요에 따라 변경할 것
'// 스트림 클로즈
streamWrite.Close
End Sub
피드 구독하기:
글 (Atom)