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)
Kpaper Create Space
글, 그림, 게임 개발, 취미, 감상 이거저거 잡다하게 올립니다.
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)