multer S3 - ν΄λ λ§λ€κΈ° / process.env.NODE_ENV
github access_token κ΄λ ¨ CROS μλ¬ (λ―Έν΄κ²°)
multerUploaderλ₯Ό s3AvatarUploaderμ s3VideoUploaderλ‘ λλ΄λ€.
// middlewares.js
const s3AvatarUploader = multerS3({
s3: s3,
bucket: "syong/avatars", // ν΄λ μμ±
acl: "public-read",
});
const s3VideoUploader = multerS3({
s3: s3,
bucket: "syong/videos", // ν΄λ μμ±
acl: "public-read",
});
export const uploadAvatar = multer({
dest: "uploads/avatars/",
limit: {
fileSize: 5000000,
},
storage: s3AvatarUploader,
});
export const uploadVideo = multer({
dest: "uploads/vidoes/",
limit: {
fileSize: 10000000,
},
storage: s3VideoUploader,
});
herokuμμ μμ ν λλ§ νμΌ μ λ‘λ μ multer S3λ₯Ό μ¬μ©νκ³ , λ΄ μ»΄ν¨ν°μμ μμ (ν μ€νΈ)ν λ λ‘컬μ μ μ₯λλλ‘ νλ €κ³ νλ€.
process.env.NODE_ENVλ productionμ΄λΌλ κ°μ κ°μ§λ, herokuμ μ μλμ΄ μλ λ³μμ΄λ€.
μ¦, herokuμμ μμ
μ€μΌ λ(μ΄ λ³μκ° κ°μ κ°μ§ λ)λ§ multer S3λ₯Ό μ¬μ©νλλ‘ νλ€.
localhostμμ μμ
μ€μΌ λ(μ΄ λ³μκ° κ°μ κ°μ§μ§ μμ λ)λ νΉλ³ν storageλ₯Ό μ¬μ©νμ§ μκ³ λ‘컬μ λ§λ€μ΄λ uploads ν΄λμ νμΌμ μ μ₯νλ€.
// middlewares.js
const isHeroku = process.env.NODE_ENV === "production";
const s3AvatarUploader = multerS3({
s3: s3,
bucket: "syong/avatars",
acl: "public-read",
});
const s3VideoUploader = multerS3({
s3: s3,
bucket: "syong/videos",
acl: "public-read",
});
export const uploadAvatar = multer({
dest: "uploads/avatars/",
limit: {
fileSize: 5000000,
},
storage: isHeroku ? s3AvatarUploader : undefined,
});
export const uploadVideo = multer({
dest: "uploads/vidoes/",
limit: {
fileSize: 10000000,
},
storage: isHeroku ? s3VideoUploader : undefined,
});
κ·Έλ°λ° μ΄λ κ² νλ©΄ λ‘컬μμ μμ ν λ avatarUrl κ°μΈ file.locationμ΄ nullμ΄ λκΈ° λλ¬Έμ μν©μ λ°λΌ file.pathκ° λ μ μλλ‘ μ½λλ₯Ό μμ νλ€.
// userController.js
export const postEdit = async (req, res) => {
// μ€λ΅
const isHeroku = process.env.NODE_ENV === "production"; // μΆκ° β
const updatedUser = await User.findByIdAndUpdate(
_id,
{
avatarUrl: file ? (isHeroku ? file.location : file.path) : avatarUrl, // μμ β
name,
email,
username,
location,
},
{ new: true }
);
// μ€λ΅
};
// videoController.js
export const postUpload = async (req, res) => {
// μ€λ΅
const isHeroku = process.env.NODE_ENV === "production"; // μΆκ° β
try {
const newVideo = await Video.create({
fileUrl: isHeroku ? video[0].location : video[0].path, // μμ β
thumbUrl: isHeroku ? thumb[0].location : thumb[0].path, // μμ β
title,
description,
hashtags: Video.formatHashtags(hashtags),
owner: _id,
});
// μ€λ΅
} catch (error) {
// μ€λ΅
});
}
return res.redirect("/");
};
λ‘컬μΈμ§ HerokuμΈμ§λ₯Ό μκΈ° μν΄ isHeroku λ³μλ₯Ό ν
νλ¦Ώμμ μ¬μ©ν μ μλλ‘ localsMiddlewareμ μ§μ νλ€.
νμΌμ΄ λ‘컬μ μ μ₯λ λλ νμΌμ΄ μλ κ²½λ‘κ° λμ§ μλλ‘ imgμ src μμ±μ "/"λ₯Ό μΆκ°ν΄μΌ νλ€.
μ€μ λ‘ μ¨μ£Όμ§λ μμλ€.
// middlewares.js
const isHeroku = process.env.NODE_ENV === "production";
export const localsMiddleware = (req, res, next) => {
res.locals.siteName = "Wetube";
res.locals.loggedIn = Boolean(req.session.loggedIn);
res.locals.loggedInUser = req.session.loggedInUser || {};
res.locals.isHeroku = isHeroku; // μΆκ° β
next();
};
π₯π₯ μ§λ¬Έ π₯π₯
λ¬Έμ λ githubλ‘ λ‘κ·ΈμΈ νμ λ github avatar μ¦, νλ‘ν μ¬μ§μ΄ μ 보μΈλ€λ κ²μ΄λ€.
3μΌκ° CORSλ§ κ²μνκ³ κ³΅λΆνμ§λ§ κ²°κ³Όμ μΌλ‘ μλ¬λ ν΄κ²°νμ§ λͺ»νλ€.
μΌλ¨ μ§κΈκΉμ§ μκ² λ κ±Έ μμλλ‘ μ 리νμλ©΄
λλ githubμμ access_tokenμ λ°μμμ userDataμ μ κ·Όν΄ νλ‘ν μ¬μ§μ κ°μ Έμ€κ³ μΆλ€.
λΈλΌμ°μ λ 보μ μμ μ΄μ λ‘ same origin policy(SOP)λ₯Ό ννκ³ μμ΄ κΈ°λ³Έμ μΌλ‘ λμΌ μΆμ²κ° μλλΌλ©΄ 리μμ€λ₯Ό 곡μ ν μ μλ€. λ€λ₯Έ μΆμ²μμ 리μμ€λ₯Ό 곡μ νλ €λ©΄ CORS(κ΅μ°¨ μΆμ² 리μμ€ κ³΅μ ) μ μ± μ μ·¨ν΄μΌ νλ€.
CORSμ λμ λ°©μμΌλ‘λ simple request, preflight request, credentials request μΈ κ°μ§κ° μλ€. λμ κ²½μ° preflight requestμ΄λ©΄μ credentials requestμλ€.
μ²μ λ¬ μλ¬λ λ€μκ³Ό κ°μλ€.
μΌλ° λ‘κ·ΈμΈμ΄λ github λ‘κ·ΈμΈμ΄λ λͺ¨λ λ‘κ·ΈμΈμμ νλ‘ν μ¬μ§μ΄ 보μ΄μ§ μκ³ μλ¬κ° λ΄λ€.
ERR_BLOCKED_BY_RESPONSE.NotSameOriginAfterDefaultedToSameOriginByCoep 200
// server.js
app.use((req, res, next) => {
res.header("Cross-Origin-Embedder-Policy", "credentialless");
res.header("Cross-Origin-Opener-Policy", "same-origin");
next();
});
//- header.pug & profile.pug
img(src=loggedInUser.avatarUrl, crossorigin="use-credentials").profile-img
~ ~ ~ has been blocked by CORS policy: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'.
GET https: //avatars.githubusercontent.com/u/~ ~ ~ net::ERR_FAILED 200
μλ¬ λ©μμ§ κ·Έλλ‘ Access-Control-Allow-Originλ₯Ό *κ° μλλΌ κ΅¬μ²΄μ μΌλ‘ λͺ
μν νμκ° μμλ€.
μ°Ύμ보λ Herokuμ Settings λ©λ΄μμ λ°λ‘ CORSλ₯Ό ꡬμ±ν μκ° μμλ€.
μλ΄ λ¬Έμλ₯Ό μ°Έκ³ ν΄ λ€μκ³Ό κ°μ΄ μμ±νλ€.
μμ§ν μ¬κΈ°κΉμ§ μ°Ύλ λ°λ§ ν΄λ μ λ§ μ€λ κ±Έλ Έλ€. credentials requestλ Access-Control-Allow-Credentialsλ₯Ό trueλ‘ μ€μ ν΄μΌ νκ³ , Access-Control-Allow-Origin'μ *λ‘ μ€μ νλ©΄ μλκ³ κ΅¬μ²΄μ μΌλ‘ μ§μ ν΄μΌ νλμ express cors middlewareλ μ€μΉν΄μ originκ³Ό credentials true μ΅μ λ λ£μ΄λ³΄κ³ , server.js νμΌμμ res.headerλ‘ μ€μ ν΄λ³΄κ³ κ·Όλ° λ trueλ λ¬Έμμ΄λ‘ μ¨μΌ νλμ§ boolean κ°μΌλ‘ μ¨μΌ νλμ§ μ°Ύμ보λ κ³³λ§λ€ λ§μ΄ νλ¦¬κ³ μ¬μν κ±° νλνλκΉμ§ μ΄ν΄κ° μλκ³ ν·κ°λ Έλ€.
μ΄λ κ² λμ΄νλκΉ μ°Έ λ³ κ±° μμ΄ λ³΄μΈλ€. λκ΅°κ°λ 10λΆ μμ λλ μλλ€μ΄μ κ² μ§λ§ μμ΄ λ¬Έμ ν΄μνκ³ μ΄λ‘ μ΄ν΄νκ³ μ€μ λ‘ λ΄ μ¬λ‘μ λ§μΆ° μ μ©νλλ° νμΈμμ΄μλ€. μμ§λ μλ²½ν μ΄ν΄ λͺ»νλλ° νλ λ°λ³΅ν΄μ λ³΄λ€ λ³΄λ μΌλΆλΆλ§ 머리μ λ°ν μν κ°λ€.
[
{
"AllowedHeaders": [
"*"
],
"AllowedMethods": [
"HEAD",
"PUT",
"GET",
"POST",
"DELETE"
],
"AllowedOrigins": [
"https://syongtube.herokuapp.com"
],
"ExposeHeaders": []
}
]
While all the actual GitHub API endpoints support CORS by sending the right response headers, it is a known issue that the
https: //github.com/login/oauth/access_token
endpoint for creating an OAuth access token does not support CORS requests from Web applications.The very specific workaround for this case is to use https: //github.com/prose/gatekeeper:
Gatekeeper: Enables client-side applications to dance OAuth with GitHub.
Because of some security-related limitations, Github prevents you from implementing the OAuth Web Application Flow on a client-side only application.
This is a real bummer. So we built Gatekeeper, which is the missing piece you need in order to make it work.
???? λ΄κ° λ§κ² μ΄ν΄ν 건κ°. λΉμ₯ κ΄λ ¨ λ¬Έμ λ₯Ό κ²μν΄λ΄€λλ° μκ°λ³΄λ€ κΈμ΄ μ λμλ€. λ΄κ° κ²μμ λͺ»νλ κ±΄μ§ μμ΄λ‘ κ²μν΄λ κΈμ΄ λ³λ‘ μμλ€. μ무λλ λ΄κ° μλͺ» μ΄ν΄ν κ±΄μ§ μ΄ μ λ³΄κ° νλ¦° 건μ§.
μ΄μ¨λ ν΄κ²°μ ν΄μΌ νκΈ°μ μΌλ¨ μλ΄μ λ°λΌ gatekeeperλ₯Ό herokuμ λ°°ν¬νλ€.
κ·Όλ° μ¬μ©λ²μ΄ νν jqueryλ‘ λμ μμλ€. jqueryλ₯Ό 1λ λͺ°λΌμ $.getJSON()μ λ°λλΌ JSλ‘ μ΄λ»κ² λ°κΎΈλμ§ κ²μν΄λ΄€λ€.
λ΄κ° μ΄ν΄ν λ°λ‘λ const json = await (await fetch()).json() νκ³ λ°μ json κ°μ μ΄μ©ν΄ μ½λλ₯Ό μ¨λκ°λ κ±΄λ° μ΄κ²λΆν° λ§κ² μ΄ν΄ν κ±΄μ§ μ ν νμ€νμ§ μκ³ μ΄μ¨λ μ½λλ₯Ό μμ νλ€.
κΈ°μ‘΄μ λ΄κ° μμ±νλ, githubμμ access_tokenμ λ°μμ€λ μ½λλ₯Ό λ€μκ³Ό κ°μ΄ κ³ μ³€λ€.
.env νμΌκ³Ό herokuμμ λ³μλ μμ ν΄μ€¬λ€.
// userController.js
export const startGithubLogin = (req, res) => {
const baseUrl = "https://github.com/login/oauth/authorize";
const config = {
client_id: process.env.OAUTH_CLIENT_ID,
allow_signup: false,
scope: "read:user user:email",
};
const params = new URLSearchParams(config).toString();
const finalUrl = `${baseUrl}?${params}`;
return res.redirect(finalUrl);
};
export const finishGithubLogin = async (req, res) => {
// μ¬κΈ°μλΆν°
const code = window.location.href.match(/\?code=(.*)/)[1];
const tokenRequest = await (
await fetch("http://localhost:9999/authenticate/" + code, {
method: "POST",
headers: {
Accept: "application/json",
},
})
).json();
// μ¬κΈ°κΉμ§ μμ
// μλλ fetch ν¨μλ‘ https://github.com/login/oauth/access_tokenμ post μμ²μ 보λ΄μ tokenRequestλ₯Ό λ°μμ€λ μ½λμλ€.
if ("token" in tokenRequest) {
const { token } = tokenRequest;
const apiUrl = "https://api.github.com";
const userData = await (
await fetch(`${apiUrl}/user`, {
headers: {
Authorization: `token ${token}`,
},
})
).json();
const emailObj = emailData.find(
(email) => email.primary === true && email.verified === true
);
if (!emailObj) {
req.flash("error", "No valid email.");
return res.redirect("/login");
}
let user = await User.findOne({ email: emailObj.email });
if (!user) {
user = await User.create({
avatarUrl: userData.avatar_url,
name: userData.name,
email: emailObj.email,
username: userData.login,
password: "",
socialOnly: true,
location: userData.location,
});
}
req.session.loggedIn = true;
req.session.user = user;
return res.redirect("/");
} else {
req.flash("error", "No access token");
return res.redirect("/login");
}
};
GET https://syongtube.herokuapp.com/users/github/finish?code=435043a20b7f90bb35ad 503 (Service Unavailable)
λ€μ μλλλ‘ μ½λλ₯Ό λλ¦¬κ³ μ΄λ²μ νλ‘μ μλ²λ₯Ό μλν΄λ΄€λ€.
cors-anywhere, http-proxy-middle λ±μ μ΄μ©ν΄ κ°νΈνκ² λ§λ€ μ μλ€κ³ ν΄μ cors-anywhereλ₯Ό μ΄μ©ν΄λ΄€λ€.
herokuμ λ°°ν¬λ₯Ό λλΈ ν λ§λ€μ΄μ§ νλ‘μ μλ² λ€μ μμ² λ³΄λΌ (access_token ꡬνλ) urlμ λΆμ¬μ fetch ν¨μλ₯Ό μ¬μ©ν΄ λ΄€μ§λ§ μμλ μ€ν¨νλ€.
μ¬κΈ°κΉμ§ μ§ννλ€κ° 503 μλ¬μ λν΄ λ€μ κ²μν΄λ΄€λλ° μ΄λ° κΈμ μ°Ύμλ€.
λ΄κ° μ½λλ₯Ό μλͺ» 건λ€μ¬μ λ‘κ·ΈμΈ'μ‘°μ°¨' μ λλ κ±°λΌκ³ μκ°νλλ° κ·Έκ² μλ 건κ°. μλλ©΄ λ΄ κ²½μ°μ ν΄λΉμ΄ μ λλ λ§μΈ κ±ΈκΉ.
What is a 503 Service Unavailable Error (And How Can I Fix It)?
An important thing to remember is that the 503 error is a server-side error. That means the problem exists with the website youβre trying to access, and not with your computer. Thatβs both good and bad news. Itβs good news because thereβs nothing wrong with your computer, and itβs bad news because thereβs usually nothing you can do to solve the problem from your end.