๊ฐ์
AWS Lambda ๋ฅผ ์ด์ฉํ์ฌ ๋์ค์ฝ๋ ์ฑ๋ด์ ๋ง๋ค ๊ฒ์ด๋ค.
๊ทธ ๊ณผ์ ์์ API Gateway, Lambda function, and Amazon SNS (Simple Notification Service) ์ ๋ํ ์ค์ต ์ฝ๋๋ฅผ ์ง์ ์์ฑํ ๊ฒ์ด๋ค.
๊ธฐ๋ฅ
- /time : ํ์ฌ ์๊ฐ ๋ณด์ฌ์ฃผ๊ธฐ
- /rice : ๊ณต๋ ์๋น ์ค๋์ ๋ฉ๋ด ๋ณด์ฌ์ฃผ๊ธฐ
- /email : ๋น์ ์ ์ ๋ณด๊ฐ ๋ด๊ธด ์ด๋ฉ์ผ ์ ์ก
๋ณธ๊ฒฉ์ ์ธ ๊ฐ๋ฐ ์์์ ์์์ developer mode ๋ฅผ ํ์ฑํ์์ผ์ผํ๋ค.
Discord UI ํ๋จ์ User Settings ๋ฒํผ์ ๋๋ฅด๊ณ Advanced ํญ์ ๊ฐ์ Developer Mode ๋ฅผ ํ์ฑํ์ํจ๋ค.
๋ด ์ค์ ํ๊ธฐ
Discord’s Developer Portal (https://discord.com/developers/applications) ์ ๊ฐ์ New Application ๋ฒํผ์ ํตํด ์ํ๋ ์ดํ๋ฆฌ์ผ์ด์ ์ ์์ฑํ๋ค.
์ข์ธก์ ์๋ OAuth2 – URL Generator ๋ฉ๋ด์ ๋ค์ด๊ฐ์ ๋ค์๊ณผ ๊ฐ์ด ์ค์ ํ ํ ๋งจ ์๋์ ์คํฌ๋กคํ์ฌ generated URL๋ฅผ ํ์ธํ๊ณ ์น ๋ธ๋ผ์ฐ์ ์์ ํค๋น ๋งํฌ์ ์ ์ํ๋ค.
์ปค๋งจ๋ ์ค์ ํ๊ธฐ
์ง๊ธ์ ๋ด์ ์๋ก์ด / ๋ช ๋ น์ด๋ฅผ ๋ฑ๋กํ ๊ฒ์ด๋ค. Discord๋ ๋ด์ด ์ด๋ค ๋ช ๋ น์ด๋ฅผ ๊ฐ์ง๊ณ ์๋์ง ์๋์ผ๋ก ํ์ ํ ์ ์์ผ๋ฏ๋ก, ์ฌ์ฉ ๊ฐ๋ฅํ ๊ฐ ๋ช ๋ น์ด๋ฅผ ์ง์ ์๋ ค์ค์ผ ํ๋ค. ์ํ๊น๊ฒ๋ ์ด๋ฅผ ์ํ ์ฌ์ฉ์ ์ธํฐํ์ด์ค(UI)๋ ์กด์ฌํ์ง ์์ผ๋ฉฐ ํ์ฌ๋ก์๋ HTTP ์๋ํฌ์ธํธ๋ฅผ ํตํด์๋ง ๊ฐ๋ฅํ๋ค. ์ด๋ฅผ ์ํด register_command.py๋ผ๋ ์ด๋ฆ์ Python ์คํฌ๋ฆฝํธ๋ฅผ ๋ง๋ ๋ค.
import requests
APP_ID = ""
SERVER_ID = ""
BOT_TOKEN = ""
url = f''
json = [
{
'name': 'time',
'description': 'Shows the current time',
'options': []
},
{
'name': 'rice',
'description': 'Provides today\'s menu at Engineering Building Cafeteria',
2024-2 Cloud Computing (01)
-4-
'options': []
},
{
'name': 'email',
'description': 'Send an email message',
'options': []
}
]
response = requests.put(url, headers={'Authorization': f'Bot {BOT_TOKEN}'}, json=json)
print(response.json())
์ดํ๋ฆฌ์ผ์ด์ ์์ธ์ ๋ณด ์ฐฝ์ ๋ช ์๋ ๊ฒ์ ๋ฐํ์ผ๋ก APP_ID(application ID), SERVER_ID, BOT_TOKEN ์ ์ฑ์ด๋ค.
๊ทธ๋ฆฌ๊ณ ๋ค์ ๋ช ๋ น์ด๋ฅผ ํตํด ์คํฌ๋ฆฝํธ๋ฅผ ์คํํ๋ค.
python3 register_command.py
๋ค์๊ณผ ๊ฐ์ ์๋ต ์ฐฝ์ด ๋จ๋ฉด ๋ฑ๋ก์ด ์ฑ๊ณต์ ์ผ๋ก ์๋ฃ๋ ๊ฒ์ด๋ค.
BeautifulSoup library์ผ๋ก ์นํ์ด์ง ํ์ฑํ๊ธฐ
์๋๋ ๊ธฐ๋ณธ์ ์ธ BeautifulSoup์ ํํ ๋ฆฌ์ผ์ด๋ค.
https://www.scrapingbee.com/blog/python-web-scraping-beautiful-soup/
์ฐ๋ฆฌ๋ ์๋ ๊ณต๋์๋น ์น์ฌ์ดํธ๋ฅผ ํ์ฑํด๋ณผ ๊ฒ์ด๋ค.
https://ewha.ac.kr/ewha/life/restaurant.do?mode=view&articleNo=905&article.offset=0&articleLimit=10
Getting the HTML
๊ฐ์ฅ ๋จผ์ , HTTP GET request ๋ฅผ ํตํ์ฌ URL๋ก๋ถํฐ contents๋ฅผ ๊ฐ์ ธ์์ผ ํ๋ค.
์์ฒญ์ ๋ณด๋ด๊ธฐ ์ํด์ Python Requests library ๋ฅผ ์ฌ์ฉํ ์ ์๋ค.
HTML data ๋ฅผ ๋นผ์จ ํ์๋ ์ํ๋ ์ ๋ณด๋ฅผ ๋นผ๋ด๊ธฐ ์ํด์ BeautifulSoup ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ด์ฉํ ์ ์๋ค.
์์ฒญ์ ๋ณด๋ด๊ธฐ ์ํด์ ํํ ๋ฆฌ์ผ์ ๋์์๋ ๊ฒ ์ฒ๋ผ ๋ค์๊ณผ ๊ฐ์ main.py ์ฝ๋๋ฅผ ์์ฑํ๊ณ ์คํ์์ผฐ๋ค.
import requests
from bs4 import BeautifulSoup
response = requests.get("https://news.ycombinator.com/")
html_content = response.content
# Use Beautiful Soup to parse the HTML
soup = BeautifulSoup(html_content, "html.parser")
๊ทธ๋ฐ๋ฐ HTML ์ ๋ฐ์์จ ๊ฒฐ๊ณผ๊ฐ ๋ค์๊ณผ ๊ฐ์ด ๊นจ์ง๋ ํ์์ ๋ฐ๊ฒฌํ ์ ์์๋ค.
์ฒ์์ ์ธ์ฝ๋ฉ ์ค์ ์ด ์๋ชป๋์๋ํ๋๋ฐ, ์๊ณ ๋ณด๋ response.encoding ์ ํ์ธํด๋ณธ ๊ฒฐ๊ณผ ๋์ผํ๊ฒ utf-8 ๋ก ์ค์ ์ด ๋์ด์์๋ค.
๋ญ๊ฐ ๋ฌธ์ ์ง.. ํ๊ณ ๊ณต์๋ฌธ์๋ฅผ ์ฐพ์๋ณด๋๋ฐ response.text ๋ฅผ ์ฌ์ฉํ๋ ์ฝ๋๋ฅผ ๋ณผ ์ ์์๋ค.
response.content ์ response.text ์ ์ฐจ์ด์ ์ด ๊ถ๊ธํด์ ์ฐพ์๋ณด์๋๋ฐ
- response.content : bytes (๋ฐ์ดํธ์ด) , ์๋ฒ์์ ์ ์ก๋ ์์ ๋ฐ์ดํฐ๋ฅผ ๊ทธ๋๋ก ๋ฐํ, ์๋ต ๋ฐ์ดํฐ์ ์ธ์ฝ๋ฉ๊ณผ ๊ด๊ณ์์ด, ๋ณํ ์์ด ์๋ณธ ๋ฐ์ดํฐ๋ฅผ ๋ฐํ
- response.text : str (๋ฌธ์์ด), ์๋ฒ์์ ์ ์ก๋ ๋ฐ์ดํฐ๋ฅผ ์ค์ ๋ ์ธ์ฝ๋ฉ์ผ๋ก ๋์ฝ๋ฉํ์ฌ ๋ฐํ, ์๋ฒ ์๋ต์ Content-Type ํค๋์ ๋ช ์๋ ์ธ์ฝ๋ฉ์ ์ฌ์ฉ
๊ฒฐ๊ตญ content ๋ raw binary data ๋ฅผ ๊ทธ๋๋ก ๋ฐํํ๊ณ text ๋ ๋์ฝ๋ฉ ํ์ ๋ฌธ์์ด์ ๋ฐํํ๋ค๋ ์ ์ด ๋ฌ๋๋ค.
๊ทธ๋์ content -> text ๋ก ๋ค์๊ณผ ๊ฐ์ด ์ฝ๋๋ฅผ ์ด์ง ์์ ํด์ฃผ์๋๋ ์ด์ ํ๊ธ๋ก ์ ๋์จ๋ค ใ ใ
import requests
# Send a GET request to the specified URL
response = requests.get("http://ewha.ac.kr/ewha/life/restaurant.do?mode=view&articleNo=905&article.offset=0&articleLimit=10")
response.encoding = 'utf-8' # ๊ฐ์ ๋ก UTF-8 ์ค์
# Check if the request was successful (status code 200)
if response.status_code == 200:
html_content = response.text
print(html_content)
else:
print(response)
์ ์ฒด ์ํคํ ์ณ ๊ตฌ์กฐ ์ค๊ณ
AWS API Gateway์ Lambda ํจ์๋ ์๋ฒ๋ฆฌ์ค ์ ํ๋ฆฌ์ผ์ด์ ๊ฐ๋ฐ์์ ํต์ฌ์ ์ผ๋ก ์ฐ๊ณ๋์ด ์ฌ์ฉ๋๋ค.
- API Gateway: ํด๋ผ์ด์ธํธ ์ ํ๋ฆฌ์ผ์ด์ (์น, ๋ชจ๋ฐ์ผ, IoT ๋ฑ)์ด ์์ฒญ์ ๋ณด๋ด๋ ์๋ํฌ์ธํธ๋ฅผ ์ ๊ณต. ์์ฒญ์ ๋ฐ์ ๋ฐฑ์๋ ์๋น์ค๋ก ์ ๋ฌํ๊ณ , ์๋ต์ ํด๋ผ์ด์ธํธ์ ๋ฐํ
- Lambda ํจ์: API Gateway๊ฐ ์ ๋ฌํ ์์ฒญ์ ์ฒ๋ฆฌํ๋ ์๋ฒ๋ฆฌ์ค ์ปดํจํ ๋ฆฌ์์ค. ์ค์ ๋ก ์์ฒญ์ ๋ํ ๋ก์ง(๋ฐ์ดํฐ ์ฒ๋ฆฌ, ๋ฐ์ดํฐ๋ฒ ์ด์ค ์์ , ์ธ๋ถ ์๋น์ค ํธ์ถ ๋ฑ)์ ์คํ
์๋ํ๋ฆ
- ํด๋ผ์ด์ธํธ(์: ๋ธ๋ผ์ฐ์ ๋๋ ๋ชจ๋ฐ์ผ ์ฑ)๊ฐ API Gateway์ ์์ฒญ(์: HTTP POST)์ ๋ณด๋ธ๋ค.
- API Gateway๋ ์์ฒญ์ Lambda ํจ์๋ก ์ ๋ฌํ๋ค.
- Lambda ํจ์๋ ์ง์ ๋ ๋น์ฆ๋์ค ๋ก์ง์ ์คํํ๊ณ ๊ฒฐ๊ณผ(์๋ต ๋ฐ์ดํฐ ๋๋ ์ค๋ฅ)๋ฅผ ๋ฐํํ๋ค.
- API Gateway๋ Lambda ํจ์์ ์๋ต์ ํด๋ผ์ด์ธํธ๋ก ์ ๋ฌํ๋ค.
์ฆ, ์ฐ๋ฆฌ๋ API Gateway ๋ฅผ ์ง์ ์ ์ผ๋ก ํ๊ณ , Lambda Function ์ ์ปดํจํ ๋ฆฌ์์ค๋ก ์ฌ์ฉํ๋ ์ดํ๋ฆฌ์ผ์ด์ ๊ตฌ์กฐ๋ฅผ ๋ง๋ค ๊ฒ์ด๋ค.
Lambda Function ์์ฑํ๊ธฐ
์ด๋ฅผ ์ํด ๊ฐ์ฅ ๋จผ์ Lambda Function์ ์์ฑํ๋ค. AWS Lambda Console ๋ก ๊ฐ์ Create function ๋ฒํผ์ ๋๋ฅธ๋ค. ๋ค์ ํ์ด์ง์์ Author from scratch ๋ฅผ ์ ํํ๊ณ function์ ์ด๋ฆ์ ๋ถ์ฌํ๋ค. ๋ฐํ์์ Python 3.8, ์ํคํ ์ณ๋ x86_64์ผ๋ก ์ค์ ํ๊ณ ํจ์๋ฅผ ์์ฑํ๋ค.
import json
import requests
from bs4 import BeautifulSoup
import datetime
from nacl.signing import VerifyKey
from nacl.exceptions import BadSignatureError
import pytz
import json
import boto3
client = boto3.client('sns')
PUBLIC_KEY = ?
PING_PONG = {"type": 1}
RESPONSE_TYPES = { "PONG": 1,
"ACK_NO_SOURCE": 2,
"MESSAGE_NO_SOURCE": 3,
"MESSAGE_WITH_SOURCE": 4,
"ACK_WITH_SOURCE": 5}
def verify_signature(event):
raw_body = event.get("rawBody")
signature = event['params']['header'].get('x-signature-ed25519')
timestamp = event['params']['header'].get('x-signature-timestamp')
verify_key = VerifyKey(bytes.fromhex(PUBLIC_KEY))
verify_key.verify(f'{timestamp}{raw_body}'.encode(), bytes.fromhex(signature))
def ping_pong(body):
if body.get("type") == 1:
return True
return False
def lambda_handler(event, context):
# verify the signature
try:
verify_signature(event)
except Exception as e:
raise Exception(f"[UNAUTHORIZED] Invalid request signature: {e}")
# check if message is a ping
body = event.get('body-json')
if ping_pong(body):
return PING_PONG
if body.get("type") == 2:
return command_handler(body)
# dummy return
return {
"type": RESPONSE_TYPES['MESSAGE_NO_SOURCE'],
"data": {
"tts": False,
"content": "BEEP BOOP",
"embeds": [],
"allowed_mentions": []
}
}
def command_handler(b):
command = b['data']['name']
if command == 'rice':
pass # Fill in here
elif command == 'time':
timeZ_S = pytz.timezone('Asia/Seoul')
dt = datetime.datetime.now(timeZ_S)
return {'type': 4,
'data': {'content': dt.strftime('%Y-%m-%d %H:%M:%S %Z %z')}
}
elif command == 'email':
pass # Fill in here
โ /rice command ์ ๋ํ์ฌ ์นํ์ด์ง๋ฅผ ํ์ฑํ๊ธฐ ์ํด BeautifulSoup library ๋ฅผ ์ด์ฉํ ๊ฒ์ด๋ค.
โก /email command ์ ๋ํ์ฌ ์ด๋ฉ์ผ์ ๋ณด๋ด๊ธฐ ์ํ์ฌ boto3 library ๋ฅผ ์ด์ฉํ ๊ฒ์ด๋ค.
Parsing the HTML with Beautiful Soup
๊ทธ๋ฐ๋ฐ BeautifulSoup ๋ฅผ ์ด์ฉํ ๋์๋ text representation์ decode ํ๋ ๊ฒ์ ๋ ์ฉ์ดํด response.content๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ๋ ์ข๋ค๊ณ ํ๋ค. character encoding ์ผ๋ก ์ธํ ์ ์ฌ์ ์ด์๋ฅผ ๋ฐฉ์งํด์ฃผ๋ ํจ๊ณผ๊ฐ ์๊ธฐ ๋๋ฌธ์ด๋ค.
BeautifulSoup ๋ HTML content ์ parser ๋ฅผ ์ธ์๋ก ๋ฐ๋๋ฐ, ์ด ๋ถ๋ถ์ content ๋ฅผ ๋ฃ์ด์ฃผ๋ฉด ๋๋ค. ๊ทธ๋ฆฌ๊ณ get_text()๋ฅผ ํตํ์ฌ ์นํ์ด์ง์ ํ ์คํธ๋ค์ ๋ชจ๋ ๋ฐ์์ฌ ์ ์๋ค.
import requests
from bs4 import BeautifulSoup
# Send a GET request to the specified URL
response = requests.get("http://ewha.ac.kr/ewha/life/restaurant.do?mode=view&articleNo=905&article.offset=0&articleLimit=10")
response.encoding = 'utf-8' # ๊ฐ์ ๋ก UTF-8 ์ค์
# Check if the request was successful (status code 200)
if response.status_code == 200:
html_content = response.content
#print(html_content)
# Use Beautiful Soup to parse the HTML
soup = BeautifulSoup(html_content, "html.parser")
# The title tag of the page
print(soup.title)
# The title of the page as string
print(soup.title.string)
# Text from the page
print(soup.get_text())
else:
print(response)
๊ฒฐ๊ณผ
Targeting DOM elements
BeautifulSoup์ ์ฌ์ฉํ์ฌ ์น ์คํฌ๋ํ์ ํ ๋ ๊ฐ์ฅ ์ค์ํ ์์ ์ค ํ๋๋ ํน์ DOM(Document Object Model) ์์๋ฅผ ๋์์ผ๋ก ํ์ฌ ์ถ์ถํ๋ ๊ฒ์ด๋ค. DOM์ ์น ๋ฌธ์๋ฅผ ์ํ ํ๋ก๊ทธ๋๋ฐ ์ธํฐํ์ด์ค์ด๋ค. ์นํ์ด์ง์ HTML์ upside-down tree๋ก์ ์์ํด๋ณด์. ๊ฐ HTML ์์(ํค๋ฉ, ๋ฌธ๋จ, ๋งํฌ ๋ฑ)๋ ์ด ํธ๋ฆฌ์ ๋ ธ๋์ด๋ค.
BeautifulSoup(BS4)๋ฅผ ์ฌ์ฉํ๋ฉด ํ์ํ DOM ์์๋ฅผ ๋น ๋ฅด๊ณ ์ฐ์ํ๊ฒ ๋์์ผ๋ก ์ค์ ํ ์ ์๋ค. ์๋๋ Beautiful Soup์ด DOM ๋ด์์ ์ด๋ฌํ ์์๋ฅผ ๋์์ผ๋ก ์ค์ ํ๋ ๋ค์ํ ๋ฐฉ๋ฒ๋ค์ด๋ค. ๋ค์๊ณผ ๊ฐ์ด ํน์ ํ๊ทธ๋ ํด๋์ค๋ฅผ ํตํด ์ํ๋ ์์์ ์ ๊ทผํ ์ ์๋ค.
soup.find("a")
soup.find_all("span")
soup.find(class_="athing")
soup.find_all(class_="athing")
์ฐ๋ฆฌ๊ฐ ๋ฐ์์์ผ ํ๋ ์ ๋ณด๋ ์ค๋ ๋ ์ง์ ํด๋นํ๋ ์ค์ ๋ฉ๋ด ๋ชฉ๋ก์ด๋ค.
๋ฐ๋ผ์ HTML ๊ฐ ์ด๋ค ๊ตฌ์กฐ๋ก ๋์ด ์๊ณ ํด๋น ์ ์ฌ ๋ฉ๋ด Text ๊ฐ ์ด๋ค ํ๊ทธ, ํด๋์ค์ ํด๋นํ๋์ง ์์์ผ ํ๋ค.
๊ฐ๋ฐ์ ๋๊ตฌ๋ฅผ ์ด์ด์ HTML ์ ํ์ธํด๋ณด๋ ul ํ๊ทธ์ class = b-menu-box ์ ์ ์ฒด ๋ฐ์ค ๋ด์ฉ์ด ๋ด๊ฒจ์๊ณ ,
๊ทธ ํ์์ ํด๋นํ๋ li ํ๊ทธ์ b-menu-day.mon ์ ๊ฐ ๋ ์ง์ ํด๋นํ๋ ๋ฐ์ค๊ฐ ๋ด๊ธด๋ค.
๊ทธ๋ฆฌ๊ณ ๊ทธ ๋ฐ์ค ๋ด๋ถ์๋ brunch ์ lunch ๋ก ๋๋์ด์ง ์์ญ(div)์ด ์๊ณ
lunch ๋ฅผ ๋ปํ๋ div -> div -> pre ๋ด๋ถ์ text ๋ฅผ ๊ฐ์ ธ์ค๋ฉด ์ค์ ๋ฉ๋ด๋ฅผ ๊ฐ์ ธ์ฌ ์ ์์๋ค!
<div class="b-day"></div>
<div class="b-menu-b"></div>
<div class="b-menu-l">
<p>์ค์</p>
<div>
<pre>์๊ณ ๊ธฐ์ค๋ ํ
์๋ฐฅ
๋๊ทธ๋๋ก์ผ์ฑ์กฐ๋ฆผ
ํ๋๊น์๋ฐ๋ณถ์
์น์ปค๋ฆฌ๊ฒ์ ์ด
๋ฐฐ์ถ๊น์น
<์ผํ>
ํ ํ์ ํ๋๊น์ค
(1.๊ฐ๋ฆญ์นฉ2.๋๊ฝ์น์ฆ3.๊ณ ๊ตฌ๋ง๋ฌด์ค)</pre>
</div>
</div>
์ฌ๊ธฐ์ ํ๊ฐ์ง ์ฃผ์ํด์ผํ ์ ์ ์ค๋ ๋ ์ง์ ํด๋นํ๋ ๋ฉ๋ด๋ฅผ ๊ฐ์ ธ์์ผ ํ๋ ๊ฒ์ธ๋ฐ,
๋ฐ๋ผ์ ์ค๋์ ์์ผ์ ๋ฐ์์จ ๋ค์์ ์ด๋ฅผ ๋ฐํ์ผ๋ก b-menu-box ์์ ๋ช๋ฒ์งธ ์์ผ ์ ๋ณด๋ฅผ ๋ฐ์์ฌ ๊ฒ์ธ์ง ๊ฒฐ์ ํ๋ค.
import requests
from bs4 import BeautifulSoup
import datetime
# Send a GET request to the specified URL
response = requests.get("http://ewha.ac.kr/ewha/life/restaurant.do?mode=view&articleNo=905&article.offset=0&articleLimit=10")
response.encoding = 'utf-8' # ๊ฐ์ ๋ก UTF-8 ์ค์
# Check if the request was successful (status code 200)
if response.status_code == 200:
html_content = response.content
# print(html_content)
# Use Beautiful Soup to parse the HTML
soup = BeautifulSoup(html_content, "html.parser")
# The title tag of the page
#print(soup.title)
# Text from the page
#print(soup.get_text())
# ์ค๋ ๋ ์ง ๊ฐ์ ธ์ค๊ธฐ
today = datetime.date.today()
# ์์ผ ๋ฒํธ ๊ฐ์ ธ์ค๊ธฐ (0: ์์์ผ, 6: ์ผ์์ผ)
weekday_number = today.weekday()
print(weekday_number)
week_menu_box = soup.find(class_="b-menu-box")
day_menu_box = week_menu_box.select_one(f'li:nth-of-type({weekday_number+1})')
day_menu_text = day_menu_box.select_one(f'.b-menu-l > div > pre').text
print(day_menu_text)
else:
print(response)
๊ฒฐ๊ณผ
Lambda Function ์์ฑํ๊ธฐ
์ ์ฒด ์์ค ์ฝ๋์ด๋ค. ํ๋ํ๋ ์๋ฏธ๋ฅผ ์ ๊น ์ดํด๋ณด๋ฉด,
def lambda_handler(event, context):
# Verify the signature
try:
verify_signature(event)
except Exception as e:
raise Exception(f"[UNAUTHORIZED] Invalid request signature: {e}")
# Check if message is a ping
body = event.get('body-json')
if ping_pong(body):
return PING_PONG
if body.get("type") == 2:
return command_handler(body)
# Dummy return
return {
"type": RESPONSE_TYPES['MESSAGE_NO_SOURCE'],
"data": {
"tts": False,
"content": "BEEP BOOP",
"embeds": [],
"allowed_mentions": []
}
}
def command_handler(b):
command = b['data']['name']
if command == 'rice':
# Send a GET request to the specified URL
response = requests.get("http://ewha.ac.kr/ewha/life/restaurant.do?mode=view&articleNo=905&article.offset=0&articleLimit=10")
response.encoding = 'utf-8' # ๊ฐ์ ๋ก UTF-8 ์ค์
if response.status_code == 200:
html_content = response.content
soup = BeautifulSoup(html_content, "html.parser")
today = datetime.date.today()
weekday_number = today.weekday()
print(weekday_number)
week_menu_box = soup.find(class_="b-menu-box")
day_menu_box = week_menu_box.select_one(f'li:nth-of-type({weekday_number+1})')
day_menu_text = day_menu_box.select_one(f'.b-menu-l > div > pre').text
return {
'type': 4,
'data': {'content': day_menu_text}
}
else:
print(response)
pass # Fill in here
elif command == 'time':
timeZ_S = pytz.timezone('Asia/Seoul')
dt = datetime.datetime.now(timeZ_S)
return {
'type': 4,
'data': {'content': dt.strftime('%Y-%m-%d %H:%M:%S %Z %z')}
}
elif command == 'email':
pass # Fill in here
์์ฒญ ์๋ช ๊ฒ์ฆ ํจ์
def verify_signature(event):
raw_body = event.get("rawBody")
signature = event['params']['header'].get('x-signature-ed25519')
timestamp = event['params']['header'].get('x-signature-timestamp')
verify_key = VerifyKey(bytes.fromhex(PUBLIC_KEY))
verify_key.verify(f'{timestamp}{raw_body}'.encode(), bytes.fromhex(signature))
- Discord์์ ๋ค์ด์ค๋ ์์ฒญ์ด ์ฌ๋ฐ๋ฅธ์ง ํ์ธํ๊ธฐ ์ํด Ed25519 ์๋ช ๊ฒ์ฆ์ ์ํ
- ๊ฒ์ฆ ์คํจ ์ ์์ธ๋ฅผ ๋ฐ์์์ผ ์์ฒญ์ ๋ฌดํจํ
- PUBLIC_KEY๋ Discord ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ณต๊ฐ ํค๋ก, ์ธ๋ถ ์์ฒญ์ ๋ฌด๊ฒฐ์ฑ์ ๋ณด์ฅ.
Ping-Pong ์ฒ๋ฆฌ ํจ์
def ping_pong(body):
if body.get("type") == 1:
return True
return False
- Discord ์๋ฒ๊ฐ ๋ณด๋ธ Ping ์์ฒญ์ธ์ง ํ์ธ
- Ping ์์ฒญ์ด๋ฉด True๋ฅผ ๋ฐํ
๋ฉ์ธ ํธ๋ค๋ฌ (lambda_handler)
def lambda_handler(event, context):
try:
verify_signature(event)
except Exception as e:
raise Exception(f"[UNAUTHORIZED] Invalid request signature: {e}")
body = event.get('body-json')
if ping_pong(body):
return PING_PONG
if body.get("type") == 2:
return command_handler(body)
return {
"type": RESPONSE_TYPES['MESSAGE_NO_SOURCE'],
"data": {
"tts": False,
"content": "BEEP BOOP",
"embeds": [],
"allowed_mentions": []
}
}
- ์๋ช ๊ฒ์ฆ: ์์ฒญ์ด Discord ์๋ฒ์์ ์จ ๊ฒ์ด ๋ง๋์ง ํ์ธ.
- Ping ์ฒ๋ฆฌ: Ping ์์ฒญ์ด๋ฉด ๋ฐ๋ก PING_PONG ์๋ต ๋ฐํ.
- ๋ช ๋ น์ด ์ฒ๋ฆฌ: type == 2์ธ ๊ฒฝ์ฐ, command_handler๋ก ๋ช ๋ น์ด๋ฅผ ์ ๋ฌ.
- ๊ธฐ๋ณธ ์๋ต: "BEEP BOOP" ๋ฉ์์ง๋ฅผ ๋ฐํ.
def command_handler(b):
command = b['data']['name']
if command == 'rice':
response = requests.get("http://ewha.ac.kr/ewha/life/restaurant.do?mode=view&articleNo=905&article.offset=0&articleLimit=10")
response.encoding = 'utf-8'
if response.status_code == 200:
html_content = response.content
soup = BeautifulSoup(html_content, "html.parser")
weekday_number = 0 # ํ์ฌ ๋ ์ง๋ฅผ ๊ธฐ์ค์ผ๋ก ์์ผ ๊ณ์ฐ ๊ฐ๋ฅ
week_menu_box = soup.find(class_="b-menu-box")
day_menu_box = week_menu_box.select_one(f'li:nth-of-type({weekday_number+1})')
day_menu_text = day_menu_box.select_one(f'.b-menu-l > div > pre').text
return {
'type': 4,
'data': {'content': day_menu_text}
}
elif command == 'time':
timeZ_S = pytz.timezone('Asia/Seoul')
dt = datetime.datetime.now(timeZ_S)
return {
'type': 4,
'data': {'content': dt.strftime('%Y-%m-%d %H:%M:%S %Z %z')}
}
elif command == 'email':
pass
command ๋ถ๊ธฐ:
- /rice:
- ์ดํ์ฌ๋ ์๋น ๋ฉ๋ด ํ์ด์ง๋ฅผ ํฌ๋กค๋งํ์ฌ ์ฃผ์ด์ง ๋ ์ง์ ๋ฉ๋ด๋ฅผ ๋ฐํ.
- HTML ํ์ฑ์ BeautifulSoup์ ์ฌ์ฉ.
- /time:
- ํ๊ตญ(์์ธ) ์๊ฐ๋๋ฅผ ๊ธฐ์ค์ผ๋ก ํ์ฌ ์๊ฐ์ ๋ฐํ.
- /email:
- ๊ตฌํ๋์ง ์์ ์ํ(์์ฑ ํ์).
์์ค์ฝ๋์ ๋ค์๊ณผ ๊ฐ์ ์ฝ๋๋ฅผ ์์ฑํ๊ณ Deploy ๋ฒํผ์ ๋๋ฅธ๋ค.
๊ทธ๋ฆฌ๊ณ AWS Lambda Console ๋ก ๋์์ Create Layer ๋ฅผ ๋๋ฅธ๋ค.
๊ทธ๋ฆฌ๊ณ Lambda ์ ์ฌ์ฉ๋ ์ฌ๋ฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ํ์ผ๋ค์ ํ๋๋ก ์์ถํ์ฌ dependency.zip ์ ๋ง๋ ๋ค.
๊ทธ๋ฆฌ๊ณ ๋ ์ด์ดํญ์ ๊ฐ์ ๋ฐฉ๊ธ ์์ฑํ dependency ๋ ์ด์ด๋ฅผ ์ถ๊ฐํด์ค๋ค.
AWS API Gateway
์ด์ ์ฐ๋ฆฌ์ ์ ํ๋ฆฌ์ผ์ด์ ์ด ํต์ ํ ์ ์๋ ์น ์๋ํฌ์ธํธ๋ฅผ ๋ง๋ค์ด์ผ ํ๋ค. ์ด๋ฅผ ์ํด API Gateway์์ ์์ ์ ์งํํ๋ค.
AWS API Gateway๋ Amazon Web Services(AWS)์์ ์ ๊ณตํ๋ ์์ ๊ด๋ฆฌํ ์๋น์ค๋ก, ๊ฐ๋ฐ์๊ฐ API(Application Programming Interface)๋ฅผ ์์ฑ, ๊ฒ์, ์ ์ง ๊ด๋ฆฌ, ๋ชจ๋ํฐ๋ง ๋ฐ ๋ณด์์ ์ค์ ํ ์ ์๋๋ก ๋๋ ํ๋ซํผ์ ๋๋ค. API Gateway๋ ๋ฐฑ์๋ ์๋น์ค์ ํด๋ผ์ด์ธํธ ์ ํ๋ฆฌ์ผ์ด์ ์ฌ์ด์ ์ค๊ฐ ์ญํ ์ ํ๋ฉฐ, HTTP, RESTful API, WebSocket API ๋ฑ์ ๋ค์ํ ์ ํ์ API๋ฅผ ์ง์ํฉ๋๋ค.
๋จผ์ ๊ธฐ๋ณธ API๋ฅผ ์์ฑํ์ฌ ๊ธฐ๋ณธ URL์ ์ป๊ณ , ๊ทธ๋ฐ ๋ค์ API ๋ด์์ ๋ฆฌ์์ค๋ฅผ ์ ์ํ์ฌ POST ์์ฒญ์ด ๊ฐ๋ฅํ ์์ ํ ์๋ํฌ์ธํธ๋ฅผ ๋ง๋ ๋ค. ๋ง์ง๋ง์ผ๋ก API๋ฅผ ๋ฐฐํฌํ์ฌ ์ธํฐ๋ท์์ ์ ๊ทผํ ์ ์๋๋ก ์ค์ ํ๋ค.
๋จผ์ API Gateway Console๋ก ์ด๋ํ๋ค. Create API -> REST API Build ํด๋ฆญ
๊ทธ๋ฌ๋ฉด ๋น ๋ฃจํธ API๋ฅผ ๋ณด์ธ๋ค. ์ด์ API๋ฅผ ์ฑ์์ผ ํ๋๋ฐ, ์ด๋ ๋ฆฌ์์ค์ ๋ฉ์๋๋ฅผ ์ ์ํ์ฌ ์ํ๋๋ค. Create resource ๋ฅผ ํตํด ์๋ก์ด ๋ฆฌ์์ค๋ฅผ ์ถ๊ฐํ ์ ์๋ค.
์ด๋ฒ์๋ Create method ๋ฅผ ๋๋ฌ ์์ HW3 ์ ๋ํ POST ๋ฉ์๋๋ฅผ ๋ง๋ ๋ค.
๊ธฐ๋ณธ ์ ๋ณด๋ฅผ ์ ๋ ฅํด์ฃผ๊ณ , API Gateway mapping template๋ฅผ ์ค์ ํด์ค๋ค.
๐ AWS API Gateway์ Mapping Template์ด๋?
ํด๋ผ์ด์ธํธ์์ ๋ค์ด์ค๋ ์์ฒญ(Request) ๋๋ ๋ฐฑ์๋์์ ๋ฐํ๋๋ ์๋ต(Response)์ ๋ฐ์ดํฐ๋ฅผ ๋ณํ(Transform)ํ๋ ๋ฐ ์ฌ์ฉ๋๋ค.
๊ทธ ๋ค์์๋ Method response ํญ์ผ๋ก ๊ฐ์ Create response ๋ฒํผ์ ๋๋ฅธ๋ค.
๊ทธ ๋ค์ ๋ค์ Resource ํญ์ผ๋ก ๋์์์ Deploy API ๋ฅผ ํด์ค๋ค.
๊ทธ๋ฆฌ๊ณ Stages ์์ Invoked URL ์ ๋ณต์ฌํ๋ค.
๊ทธ๋ฆฌ๊ณ ๋ค์ Developer Portal ๋ก ๋์์์ INTERACTIONS ENDPOINT URL ์ ๋ฐฉ๊ธ ๋ณต์ฌํ URL ์ ๋ถ์ฌ๋ฃ๋๋ค.
๊ทธ๋ฆฌ๊ณ Save changes ๋ฅผ ๋๋ฅด๋ฉด ๋ค์๊ณผ ๊ฐ์ ๊ฒฝ๊ณ ๊ฐ ๋ฌ๋ค.
๊ทธ๋ฐ๋ฐ ์ ๋๋ก ๋์ํ์ง ์์์ CloudWatch ์์ lambda ๊ด๋ จ ๋ก๊ทธ๋ฅผ ์ดํด๋ณด์์ผํ๋ค.
Lambda ํจ์๋ ๊ธฐ๋ณธ์ ์ผ๋ก ์คํ ์ ๊ด๋ จ ๋ก๊ทธ ๊ทธ๋ฃน์ ์๋ ์์ฑํ๋ค. ๊ทธ๋ฌ๋ ์๋ฌ ๋ก๊ทธ๋ฅผ ์ดํด๋ณด๊ธฐ ์ ์ CloudWatch ์ lambda ๊ฐ ๊ธฐ๋ณธ์ ์ผ๋ก ์ฐ๋์ด ์ ์๋๋ ๋ฌธ์ ๊ฐ ์์ด์ Monitor ํญ์ ํ์ธํด๋ณด์๋๋,
CloudWatch Logs์์ ์ง์ ๋ ๋ก๊ทธ ๊ทธ๋ฃน(/aws/lambda/HW3Discord)์ด ์กด์ฌํ์ง ์๋๋ค๋ ์๋ฌ๊ฐ ๋ด๋ค.
๊ทธ๋์ IAM ๊ถํ์ ๋ค์ ํ์ธํด์ฃผ์๋๋ ์ด๋ฒ์ UNAUTHORIZED ์๋ฌ๊ฐ ๋ฐ์ํ๋ค.
์๊ณ ๋ณด๋ POST ์ชฝ์ URL ์ ๋ณต์ฌํด์ผํ๋๋ฐ root ๊ฒฝ๋ก์ ์๋ URL ์ ๋ณต์ฌํด์ ์๊ธด ๋ฌธ์ ์๋ค..ใ ใ
์ด์ ๋ก๊ทธ๋ ์ ๋๋ก ๋ฌ๋ค.
๋ณ๊ฒฝ์ด ์ ๋๋ ๊ฒ์ ํ์ธํด์ค๋ค!
์ด์ ๋์ค์ฝ๋์ ์ ์ํ์ฌ ๋ด์ผ๋ก ๋ช ๋ น์ด๋ฅผ ์์ฑํด๋ณด๋, time ๋ช ๋ น์ด๋ง ๋จน๊ณ ๋๋จธ์ง๋ ์ ๋๋ก ์๋ต์ด ๋์ง ์๋๋ค..
Simple Notification Service
Lambda function ์ผ๋ก๋ถํฐ email ์ ๋ณด๋ด๊ธฐ ์ํด Amazon SNS ๋ฅผ ํ์ฉํ๋ค. Standard Type ์ผ๋ก Topic ์ ํ๋ ์์ฑํ๋ค.
๋ฉ์ผ ์ธ์ฆ์ ํด์ฃผ๋ฉด ๊ตฌ๋ ์ฃผ์๋ฅผ ํ์ธํ ์ ์๋ค.
์ด์ SNS ๋ฅผ lambda ์์ ์ฌ์ฉํ ์ ์๋๋ก ์ฐ๊ด์ํฌ ๊ฒ์ธ๋ฐ, Add destination ์ ๋๋ฌ์ Amazon SNS ๋ฅผ ์ถ๊ฐํด์ค๋ค.
Lambda ํจ์์ IAM ์ญํ ์ด ๋ก๊ทธ ์์ฑ ๊ถํ
official document: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/sns/client/publi sh.html
'CS > ํด๋ผ์ฐ๋ ์ปดํจํ ' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Cloud Computing] AWS Advanced Details (0) | 2024.09.26 |
---|