Course-এর সূচি

10.3

Static Website Hosting আর CORS

bucket থেকে ওয়েবসাইট চালানো, 403-এর ফাঁদ, আর preflight সহ cross-origin request।

18 মিনিট পড়া + Animation

টিপস: ডটেড-আন্ডারলাইন করা যেকোনো শব্দে ট্যাপ বা Hover করলে তার অর্থ দেখবে।

এই লেসন শেষে তুমি পারবে

  • Static website কী, কোনো server ছাড়াই S3 কেন সেটা চালাতে পারে, আর কখন এটাই সবচেয়ে সস্তা সঠিক উত্তর, সেটা বোঝাতে।
  • একটা bucket-কে website বানাতে: hosting চালু করা, index আর error document ঠিক করা, public access খুলে দেওয়া, আর সবার জন্য read-এর bucket policy লেখা।
  • Website endpoint-এর দুই রকম format চিনতে, আর REST endpoint-এর সাথে website endpoint-এর তফাত বলতে: শুধু HTTP, শুধু public read, HTML error, index document আর redirect।
  • শুধু status code দেখেই 403-এর ফাঁদ আর বাকি চেনা error (404, 302, 400, HTTPS-এর ব্যর্থতা, SSE-KMS, Requester Pays) ধরতে।
  • তিন রকম redirect, Route 53 দিয়ে নিজের domain, আর CloudFront বা Amplify Hosting দিয়ে HTTPS সাজাতে।
  • Origin, same-origin policy আর CORS বোঝাতে, browser কখন preflight পাঠাবে আগেই বলতে, আর এমন CORS configuration লিখতে যেটা page-এর যা লাগে ঠিক ততটুকুই অনুমতি দেয়।

#Server ছাড়া একটা website

#“Static” মানে কী

Static website হলো কিছু file-এর সমষ্টি, যেগুলো যেমন রাখা আছে ঠিক তেমনই browser-এ পাঠানো হয়: HTML page, CSS style sheet, JavaScript file, ছবি, font, .pdf download। প্রত্যেক ক্রেতার জন্য server-এ আলাদা করে কিছু হিসাব করা হয় না। Dynamic site-এ প্রতিটা request-এ server-এ code চলে (PHP, Node.js, Python, Java…): page বানাতে, database পড়তে বা login যাচাই করতে।

“Static” মানে কিন্তু “একঘেয়ে” না। React, Angular বা Vue দিয়ে বানানো আধুনিক single-page application (SPA) compile হয়ে static file-ই হয়ে যায়। সব interactivity চলে JavaScript হিসেবে browser-এর ভেতরে, আর dynamic data-র জন্য সে অন্য জায়গার API-কে call করতে পারে (API Gateway, Lambda, একটা ALB)। File-গুলো নিজেরা static, তাই S3 সেগুলো দিতে পারে।

S3 যেটা পারে না, সেটা হলো server-এর দিকে code চালানো: PHP না, Node.js না, Python না, server-side rendering না, database query না, form handler না। কোনো page যদি প্রতি request-এ বানাতে হয়, সেই অংশের জন্য লাগবে EC2, container বা Lambda (chapter ১৫)।

#খরচ কত

Website hosting একটা setting মাত্র, আলাদা কোনো product না, তাই এর নিজের কোনো fee নেই। তুমি S3-এর স্বাভাবিক দামই দাও: প্রতি GB-month storage, প্রতিটা GET request-এর জন্য সামান্য কিছু, আর internet-এ data transfer out-এর জন্য প্রতি GB (খরচের ভাগগুলো লেসন ১০.১-এ আছে)। ভিড়ওয়ালা site-এ সামনে CloudFront বসালে সাধারণত খরচ কমে, গতিও বাড়ে, কারণ S3 থেকে CloudFront-এ transfer বিনা পয়সায়, আর বারবার আসা ক্রেতাদের edge cache থেকেই দেওয়া হয়।

#একটা bucket-কে website বানানো

  1. Static website hosting চালু করো

    Bucket-এর Properties-এ Static website hosting-এর নিচে Enable আর Host a static website বেছে নাও। (অন্য বিকল্প “Redirect requests for an object” পুরো bucket-টাকেই একটা redirect বানিয়ে দেয়; নিচে “Redirect” অংশ দেখো।)

  2. Index document-এর নাম দাও, চাইলে error document-এরও

    Index document (সাধারণত index.html) বাধ্যতামূলক: root আর slash দিয়ে শেষ হওয়া যেকোনো path-এর জন্য S3 এটাই দেয়। Error document (যেমন 404.html) ঐচ্ছিক। দুটো নামেই বড়-ছোট হাতের অক্ষর আলাদা ধরা হয়, আর upload করা object-এর নামের সাথে হুবহু মিলতে হবে। এরপর site-এর file-গুলো upload করো।

  3. Block Public Access বন্ধ করো

    নতুন bucket-এ Block Public Access-এর চারটা setting-ই চালু থাকে (লেসন ১০.২), তোমার account-এও থাকতে পারে। এই bucket-এর জন্য সেগুলো তুলে দাও, আর account স্তরে চালু থাকলে সেখানেও: S3 দুটোর মধ্যে সবচেয়ে কড়া মিলটাই মানে।

  4. সবার জন্য read-এর একটা bucket policy দাও

    Bucket-এর object-গুলোতে সবাইকে s3:GetObject দাও (শেষের /* খেয়াল করো):

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Sid": "PublicReadGetObject",
          "Effect": "Allow",
          "Principal": "*",
          "Action": "s3:GetObject",
          "Resource": "arn:aws:s3:::mishti-menu-example/*"
        }
      ]
    }
    

Policy-টা কী দেয় না, সেটাও দেখো: s3:ListBucket। ক্রেতা নাম জানা যেকোনো object পড়তে পারে, কিন্তু bucket-এর list দেখতে পারে না। ঠিক এটাই চাই।

Notes-এ শর্তটা এক লাইনে বলা: bucket-এ bucket policy দিয়ে public read access চালু থাকতে হবে। আজকের দিনে আরও দুটো কথা জানা দরকার। এপ্রিল ২০২৩ থেকে নতুন bucket-এ ACL বন্ধ থাকে (Object Ownership = bucket owner enforced), তাই object public করার একমাত্র উপায় bucket policy, আর সেটা যে-ই upload করুক, সব object-এর ওপর খাটে। কিন্তু পুরোনো কোনো bucket-এ ACL চালু থাকলে, অন্য account-এর upload করা object-এর মালিক ওই account, তাদের ওপর bucket policy খাটে না; তাদের public-read ACL লাগবে, অথবা আরও ভালো হয় bucket-টাকে bucket owner enforced-এ নিয়ে গেলে।

#Website endpoint

Website bucket উত্তর দেয় একটা website endpoint-এ, যেটা S3-এর সাধারণ API (REST) endpoint থেকে আলাদা। Region অনুযায়ী এর format দুই রকম:

Formatউদাহরণকোন Region
s3-website- dash Regionhttp://mishti-menu-example.s3-website-us-east-1.amazonaws.comপুরোনো Region-গুলো: us-east-1, us-west-1, us-west-2, eu-west-1, ap-southeast-1, ap-southeast-2, ap-northeast-1, sa-east-1 (আর GovCloud US-West)
s3-website dot Regionhttp://mishti-menu-example.s3-website.ap-south-1.amazonaws.comবাকি সব Region, নতুনগুলো সহ (ap-south-1, eu-central-1, us-east-2…)

দুটোই শুরু হয় http:// দিয়ে, কখনো https:// না। Bucket-এর নাম আসে সবার আগে, তাই bucket-এর নামের মতোই endpoint-ও সারা পৃথিবীতে একটাই। কোন Region কোন চিহ্ন ব্যবহার করে মুখস্থ করার দরকার নেই; পরীক্ষা শুধু চায় তুমি দুটো চেহারাকেই S3 website endpoint হিসেবে চিনতে পারো, আর console-এ Static website hosting-এর নিচে ঠিকটাই দেখায়।

Website endpoint-এ একটা request

একজন ক্রেতা মিষ্টি ঘরের menu site খুলল। Browser সাধারণ HTTP দিয়ে bucket-এর website endpoint-এর সাথে কথা বলে; endpoint path-টাকে object key বানায়, object-টা public কি না দেখে, তারপর HTML দিয়ে উত্তর দেয়।

পরিস্থিতি

Hosting চালু, Block Public Access বন্ধ, সবার জন্য read policy: স্বাভাবিক অবস্থা।

ap-south-1Mumbai
Browserঢাকায়
Website endpointশুধু HTTP
mishti-menu-examplebucket
GET /

Bucket-এর setting

Block Public AccessBucket policyIndex documentError document
Offসবার জন্য read, /*index.html404.html
ধাপ 1 / 4
1

ক্রেতা website endpoint লিখল: mishti-menu-example.s3-website.ap-south-1.amazonaws.com, কোনো path ছাড়া। Browser port 80-এ সাধারণ HTTP দিয়ে GET / পাঠাল।

#Index document আর folder

S3-এ আসল folder নেই (লেসন ১০.১), কিন্তু website endpoint slash-ওয়ালা key-গুলোকে folder-এর মতো আচরণ করায়:

  • / আর /menu/ (শেষে slash) ফেরত দেয় ওই “folder”-এর index document: index.html আর menu/index.html। প্রতিটা folder-এ আলাদা index document লাগে, একই নামে।
  • /menu (শেষে slash নেই): S3 আগে খোঁজে ঠিক menu নামের key-র কোনো object আছে কি না। না থাকলে, কিন্তু menu/index.html থাকলে, সে 302 Found দিয়ে /menu/-এ পাঠিয়ে দেয়। দুটোর কোনোটাই না থাকলে 404।
  • Versioning চালু bucket-এ index.html-এর শুধু সবচেয়ে নতুন version-টাই দেওয়া হয়।

#Error document

কিছু ভুল হলে website endpoint একটা HTML page ফেরত দেয় (REST endpoint দেয় XML)। Error document সেট করা থাকলে প্রতিটা 4xx error-এ S3 তোমার page পাঠায়, আসল status code সহ; 5xx error-এ সবসময় S3-এর নিজের page যায়। Error document-কেও সবার পড়ার মতো হতে হবে, নইলে ক্রেতা S3-এর সাদামাটা page-ই দেখবে।

#403-এর ফাঁদ, আর বাকি error

#403 Forbidden কেন আসে

Slide-এর চেনা পরামর্শ: 403 Forbidden পেলে দেখো bucket policy public read-এর অনুমতি দিচ্ছে কি না। বাস্তবে কারণ আরও কয়েকটা আছে। সবগুলোর গোড়ায় একটাই কথা: website-এর ক্রেতা anonymous, তাই একটা object তখনই দেওয়া হয়, যখন anonymous read অনুমোদিত আর পথে আর কিছু আটকায় না।

কারণকেন ব্যর্থ হয়সমাধান
Block Public Access চালু (bucket বা account-এ)Public policy save করা যায় না, আর আগে save করা থাকলেও উপেক্ষা হয়দুই স্তরেই এই bucket-এর দরকার মতো বন্ধ করো
কোনো bucket policy নেইObject default-এ private; শুধু Block Public Access বন্ধ করলে কিছুই public হয় নাসবার জন্য read-এর policy দাও
Policy-র Resource হলো /* ছাড়া arn:aws:s3:::bucketএটা bucket-কে বোঝায়, তার object-গুলোকে নাarn:aws:s3:::bucket/* লেখো
Object-এর মালিক অন্য account (ACL চালু পুরোনো bucket)Bucket policy শুধু bucket owner-এর নিজের object-এ খাটেBucket owner enforced, বা ওই object-গুলোতে public-read ACL
Object SSE-KMS দিয়ে encrypt করাDecrypt করতে অনুমোদিত কোনো পরিচয় থেকে KMS-কে call করতে হয়; anonymous ক্রেতার তেমন কিছু নেইPublic content-এ SSE-S3, অথবা OAC আর key policy সহ CloudFront (লেসন ১০.৮)
Requester Pays চালুAnonymous ক্রেতাকে বিল করা যায় না; website endpoint এর সাথে কখনোই চলে নাবন্ধ করো (লেসন ১০.৭)
Bucket policy-তে স্পষ্ট একটা Deny (যেমন aws:SourceIp দিয়ে)Deny সবসময় জেতে (লেসন ২.২)শর্তটা ঠিক করো

#404 আর বাকি status code

StatusWebsite endpoint কখন দেয়
200 OKObject (বা index document) আছে, আর সবাই পড়তে পারে
301 Moved Permanentlyএকটা redirect rule, “সব request redirect” করা bucket, বা redirect metadata-ওয়ালা object
302 FoundFolder চাওয়া হয়েছে শেষের slash ছাড়া
304 Not ModifiedBrowser-এর cache-এ রাখা কপিটাই এখনো ঠিক আছে (conditional request)
400 Bad RequestRequest এই bucket-এর ভুল Region-এর website endpoint-এ গেছে
403 ForbiddenObject আছে, কিন্তু সবার পড়ার মতো না (ওপরের table দেখো)
404 Not FoundKey নেই, index document নেই, bucket নেই, বা bucket website হিসেবে configure করা নেই
405 Method Not AllowedGET বা HEAD ছাড়া অন্য যেকোনো কিছু
500 / 503ভেতরের কোনো error, অথবা “slow down”: request-এর হার কমাও (লেসন ১০.৬)

একটা পার্শ্বপ্রতিক্রিয়া জেনে রাখো: public website নেই এমন key-তে 404 দেয় আর private key-তে 403, ফলে যে কেউ বুঝে ফেলতে পারে কোন key আছে। এটা সমস্যা হলে website hosting চালুই কোরো না; bucket-কে CloudFront-এর পেছনে private রাখো।

প্রতিটা কারণ নিজে ঘটিয়ে দেখো। নতুন bucket যেমন setting নিয়ে আসে, সেখান থেকে শুরু করো, তারপর ধাপে ধাপে 200 পর্যন্ত পৌঁছাও:

Website tester: 403 কেন?

Bucket সাজাও, একটা endpoint আর একটা path বাছো, তারপর একটা anonymous request পাঠাও, ঠিক যেভাবে ক্রেতার browser পাঠায়। এই লেসনের প্রতিটা 403 আর 404 নিজে ঘটিয়ে দেখার চেষ্টা করো।

Bucket mishti-menu-example

Object: index.html, about.html, 404.html, menu/index.html

Bucket policy

Object-এর encryption

Path

উত্তর দেখতে “Request পাঠাও” চাপো।

#Website endpoint না REST endpoint

Website bucket-এর সাধারণ REST endpoint-ও থেকে যায়, https://mishti-menu-example.s3.ap-south-1.amazonaws.com, আর দুটোর আচরণ একদম আলাদা:

REST endpointWebsite endpoint
কার জন্যProgram: S3 API, SDK, CLIBrowser
Access controlPublic আর private দুই রকম content (signed request, IAM)শুধু সবার পড়ার মতো content
RequestBucket আর object-এর সব operationObject-এ শুধু GET আর HEAD
GET /Bucket-এর list দেয় (অনুমতি থাকলে)Index document দেয়
Folder (/menu/)শুধুই একটা key; index document নেইmenu/index.html
ErrorXMLHTML, চাইলে তোমার error document
Redirectনেই (redirect metadata-ওয়ালা object যেমন আছে তেমন ফেরত আসে)Bucket আর object দুই স্তরের redirect
HTTPSআছেনেই, শুধু HTTP
Access point, Requester Paysচলেচলে না

একই bucket, দুটো endpoint

Website hosting চালু করা bucket দুটো আলাদা hostname-এ উত্তর দেয়। REST endpoint হলো সাধারণ S3 API; website endpoint হলো browser-এর জন্য একটা ছোট web server। Object একই, আচরণ আলাদা।

পরিস্থিতি

Browser
Website endpoint…s3-website.ap-south-1…
REST endpoint…s3.ap-south-1…
GET / (HTTP)
ধাপ 1 / 4
1

Website endpoint-এ root মানে index document।

নিজেকে যাচাই করোপ্রশ্ন

একজন developer browser-এ https://mishti-menu-example.s3.ap-south-1.amazonaws.com/ খুলল, home page দেখবে ভেবে। Bucket-এ website hosting আর সবার জন্য read policy আছে। Browser কী দেখাবে?

#Redirect

Website endpoint তিনভাবে ক্রেতাকে অন্য জায়গায় পাঠাতে পারে:

  1. সব request অন্য host-এ redirect। পুরো একটা bucket-এর hosting option “Redirect requests for an object” করে একটা host name দাও (চাইলে http বা https-ও)। চেনা ব্যবহার হলো www.example.com → example.com: দুই নামের দুটো bucket, content একটায়, অন্যটা ফাঁকা আর redirect করে, path অপরিবর্তিত রেখে।
  2. Routing rule, website configuration-এর ভেতরে (console-এ JSON, সর্বোচ্চ ৫০টা rule)। প্রতিটা rule-এর একটা শর্ত থাকে, key prefix (KeyPrefixEquals) আর/অথবা error code (HttpErrorCodeReturnedEquals), আর একটা redirect: নতুন host, protocol, বদলি prefix (ReplaceKeyPrefixWith) বা বদলি key (ReplaceKeyWith), আর redirect code। Folder-এর নাম বদলালে, কোনো অংশ তুলে দিলে, বা 404-গুলো এমন server-এ পাঠাতে চাইলে যে সেগুলো সামলাতে পারে, তখন এটা কাজে লাগে।
  3. Object redirect: object-এর x-amz-website-redirect-location metadata-তে আরেকটা key (শুরুতে / সহ) বা পুরো একটা URL দাও। Website endpoint 301 দেয়; REST endpoint এটা পাত্তা দেয় না, object-টাই ফেরত দেয়। ৫০ rule-এর কোনো সীমা নেই, তাই আলাদা আলাদা অনেক page সরালে এটাই মানায়।
[
  {
    "Condition": { "KeyPrefixEquals": "docs/" },
    "Redirect": { "ReplaceKeyPrefixWith": "documents/" }
  },
  {
    "Condition": { "HttpErrorCodeReturnedEquals": "404" },
    "Redirect": { "HostName": "app.example.com", "ReplaceKeyPrefixWith": "report-404/" }
  }
]

তিন রকম redirect

Website bucket সবকিছু অন্য host-এ পাঠাতে পারে, নিয়ম ধরে পাঠাতে পারে (key prefix বা error code দেখে), অথবা metadata দিয়ে একটা object-কে পাঠাতে পারে।

পরিস্থিতি

Browser
www.example.comশুধু redirect
example.comআসল content
App serverdynamic page
সব request → example.com
ধাপ 1 / 5
1

example.com আর www.example.com দুটোই চালাতে হলে ঠিক ওই দুই নামে দুটো bucket বানাতে হয়। Content থাকে example.com-এ; www bucket ফাঁকা, আর তাকে সেট করা হয় host example.com-এ “redirect requests for an object” দিয়ে।

#নিজের domain, আর HTTPS

#Route 53 দিয়ে নিজের domain

ক্রেতারা লিখবে example.com, লম্বা S3 hostname না। Notes বলছে নিজের domain-এর জন্য CloudFront লাগে; কথাটা অর্ধেক সত্যি। শুধু নিজের domain-এর জন্য CloudFront লাগে না:

  • Bucket-এর নাম রাখো domain-এর হুবহু নামে: example.com (আর www redirect bucket-এর জন্য www.example.com)।
  • Route 53-এ example.com-এর জন্য একটা Alias A record বানাও, target হবে S3 website endpoint (লেসন ৮.২)। Subdomain হলে যেকোনো DNS provider-এ website endpoint-এর দিকে একটা CNAME-ও চলে।

এটা কাজ করে কারণ S3 HTTP request-এর Host header দেখে bucket বেছে নেয়। এর মানে এটাও: example.com নামের bucket অন্য কারো হলে এই পদ্ধতি অসম্ভব, আর নাম না মিললে bucket-টা Alias target হিসেবে দেখাই যায় না।

#HTTPS-এর জন্য CloudFront (বা Amplify Hosting)

Website endpoint-এ HTTPS একেবারেই নেই, আর এতে certificate লাগানোও যায় না। আজকাল browser HTTP site-কে “Not secure” দেখায়, আর অনেক সুবিধা (service worker, geolocation, clipboard) HTTPS ছাড়া চলে না। বিকল্পগুলো:

  • Website endpoint-কে custom origin বানিয়ে CloudFront। CloudFront example.com-এর জন্য বিনা পয়সার একটা ACM certificate (CloudFront-এর জন্য us-east-1-এ চাওয়া) দিয়ে HTTPS চালায়; Route 53 Alias দেখায় distribution-কে। প্রতিটা folder-এর index document আর redirect থেকে যায়, কিন্তু bucket public-ই থাকে, আর CloudFront থেকে S3-এর ধাপটা HTTP।
  • REST endpoint আর Origin Access Control (OAC) দিয়ে CloudFront। Website hosting বন্ধ, Block Public Access চালু, bucket policy শুধু ওই distribution-কে অনুমতি দেয়। শুরু থেকে শেষ HTTPS, CloudFront এড়িয়ে কেউ ঢুকতে পারে না, আর SSE-KMS-ও চলে। /-এর জন্য একটা default root object সেট করতে হয়, আর sub-folder-এর index page আর redirect সামলাতে হয় CloudFront Functions দিয়ে। বেশিরভাগ নতুন site-এর জন্য AWS এটাই বলে, আর chapter ১১-এ এর বিস্তারিত আছে (CloudFront basics)।
  • AWS Amplify Hosting (পাশের কথা)। S3-এর documentation এখন S3-এ রাখা site-এ HTTPS দেওয়ার সবচেয়ে সহজ উপায় হিসেবে এটাকেই বলে: তুমি bucket আর prefix বেছে দাও, Amplify file-গুলো CloudFront-চালিত একটা CDN-এ বসিয়ে দেয়, HTTPS URL আর নিজের domain সহ। SAA-C03 পরীক্ষায় এটা নেই; সেখানে “S3 static website-এ HTTPS”-এর উত্তর CloudFront।

নিজের domain, আর HTTPS

Route 53 সরাসরি example.com-কে HTTP-তে একটা website bucket-এর দিকে দেখাতে পারে। HTTPS চাইলে, বা bucket private রাখতে চাইলে, সামনে বসে CloudFront।

পরিস্থিতি

ap-south-1
ক্রেতা
Route 53example.com
example.combucket
example.com?
ধাপ 1 / 3
1

ঠিক example.com নামের একটা bucket-এ website hosting চালু। Route 53-এ example.com-এর একটা Alias A record সেই bucket-এর website endpoint-কে দেখায় (লেসন ৮.২)। নিজের domain-এর জন্য CloudFront লাগেই না।

#CORS: page আর file যখন আলাদা জায়গায়

#Origin আর same-origin policy

একটা URL-এর origin হলো তিনটা জিনিস একসাথে: scheme + host + port:

  • https://www.example.com-এর scheme https, host www.example.com, আর port ধরে নেওয়া 443 (http হলে 80)।
  • https://www.example.com/app1 আর https://www.example.com/app2 একই origin: path গোনায় পড়ে না।
  • https://www.example.com আর https://other.example.com আলাদা origin (host আলাদা); তেমনই http://www.example.com (scheme আলাদা) আর https://www.example.com:8443 (port আলাদা)।

Browser same-origin policy মেনে চলে: এক origin থেকে আসা script অন্য origin-এ request পাঠাতে পারে, কিন্তু অন্য origin স্পষ্ট করে অনুমতি না দিলে তার উত্তর পড়তে পারে না। এই নিয়ম না থাকলে তুমি যে site-এই ঢুকতে, সেটা তোমার login করা session ব্যবহার করে তোমার ব্যাংকের page পড়ে ফেলতে পারত। CORS (Cross-Origin Resource Sharing) হলো অন্য origin-এর বলার উপায় যে “এই origin আমাকে পড়তে পারে”: Access-Control-Allow-Origin-এর মতো response header দিয়ে।

তিনটা কথা অনেকেই খেয়াল করে না:

  • CORS মানায় browser, S3 না। একই file নতুন tab-এ, curl দিয়ে বা কোনো server থেকে দিব্যি খোলে। S3 শুধু browser-এর প্রশ্নের উত্তর দেয়।
  • এটা খাটে script-এর (fetch, XMLHttpRequest) cross-origin পড়ায়, CSS-এর load করা web font-এ, আর crossorigin attribute দিয়ে ব্যবহার করা ছবি বা video-তে (যেমন canvas-এ আঁকা)। অন্য origin-এর ছবি সাধারণ <img src> দিয়ে দেখাতে কোনো CORS লাগে না।
  • CORS কখনো permission দেয় না। S3 object দেবে কি না, সেটা তবুও ঠিক করে bucket policy, IAM বা signature; CORS শুধু ঠিক করে browser script-কে উত্তরটা দেখতে দেবে কি না।

#Simple request আর preflight

Browser cross-origin request-কে দুই ভাগে ভাগ করে:

  • Simple request: GET, HEAD বা POST, শুধু “নিরাপদ” header আর সাধারণ একটা Content-Type (form data বা text) সহ। Browser সাথে সাথে request পাঠায়, একটা Origin header জুড়ে, তারপর response-এ Access-Control-Allow-Origin খোঁজে। না থাকলে বা অন্য origin-এর নাম থাকলে, S3 data পাঠালেও script পায় CORS error।
  • Preflight করা request: অন্য যেকোনো method (PUT, DELETE), একটা custom header (Authorization, x-amz-*), বা application/json-এর মতো Content-Type। আসল request-এর আগে browser একটা OPTIONS request পাঠায়, এটাই preflight, এতে থাকে Origin, Access-Control-Request-Method আর Access-Control-Request-Headers। উত্তর যদি origin, method আর প্রতিটা header-এর অনুমতি দেয়, তবেই আসল request যায়।

Slide-এ preflight-এর ছবিটা এমন: browser cross-origin server-কে জিজ্ঞেস করে “https://www.example.com কি GET, PUT, DELETE পাঠাতে পারবে?”; উত্তরে আসে Access-Control-Allow-Origin: https://www.example.com আর Access-Control-Allow-Methods: GET, PUT, DELETE; তারপর আসল request যায়। Access-Control-Max-Age header থাকলে browser এই উত্তর cache-এ রেখে পরের preflight বাদ দিতে পারে (browser একটা সীমা টানে; Chrome-এ ২ ঘণ্টা)।

একটা site আর একটা bucket-এর মধ্যে CORS

Page আসে https://www.example.com থেকে। তার JavaScript data চায় assets-example bucket থেকে, S3-এর REST endpoint দিয়ে, যেটা আলাদা origin। নিয়ম মানায় browser; S3 শুধু header পাঠিয়ে জানায় সে কী অনুমতি দেয়।

পরিস্থিতি

www.example.compage-এর origin
BrowserJS চালায়
assets-exampleঅন্য origin
GET /
ধাপ 1 / 4
1

Browser দোকানের page আনল। তার origin হলো scheme + host + port: https://www.example.com (port 443 ধরা আছে)।

#Bucket-এ CORS configuration

Configuration দিতে হয় সেই bucket-এ, যে resource দিচ্ছে, মানে cross-origin target-এ; যে site request করছে তাতে কখনো না। S3 console-এ এটা JSON (Permissions → Cross-origin resource sharing); API আর CLI-ও এটা নেয় (put-bucket-cors), আর পুরোনো document-এ একই নিয়ম XML-এ দেখা যায়। একটা configuration-এ সর্বোচ্চ ১০০টা rule, প্রত্যেকটায়:

Elementমানে
AllowedOrigins (বাধ্যতামূলক)কোন origin অনুমোদিত, যেমন https://www.example.com। প্রতিটা string-এ একটা * wildcard থাকতে পারে (https://*.example.com), আর শুধু * দিলে সব origin চলে
AllowedMethods (বাধ্যতামূলক)GET, PUT, POST, DELETE, HEAD-এর যেকোনোটা
AllowedHeadersPreflight-এ browser কোন header পাঠানোর অনুমতি চাইতে পারে (Access-Control-Request-Headers); প্রতিটা string-এ একটা *, তাই x-amz-* বা *
ExposeHeadersকোন response header script পড়তে পারবে, যেমন ETag (browser থেকে multipart upload-এ লাগে) বা x-amz-meta-* value
MaxAgeSecondsPreflight-এর উত্তর browser কতক্ষণ cache-এ রাখতে পারবে
IDRule-এর একটা ঐচ্ছিক নাম

S3 rule-গুলো ক্রমানুসারে দেখে, আর প্রথম যে rule-এ origin, method আর (preflight হলে) চাওয়া প্রতিটা header মেলে, সেটাই ব্যবহার করে। কোনোটাই না মিললে preflight পায় 403 Forbidden (“This CORS request is not allowed”, অথবা configuration একেবারে না থাকলে “CORS is not enabled for this bucket”), আর browser আসল request পাঠায়ই না।

[
  {
    "ID": "public-read-anywhere",
    "AllowedOrigins": ["*"],
    "AllowedMethods": ["GET", "HEAD"],
    "AllowedHeaders": []
  },
  {
    "ID": "admin-uploads",
    "AllowedOrigins": ["https://www.example.com", "https://admin.example.com"],
    "AllowedMethods": ["PUT", "POST", "DELETE"],
    "AllowedHeaders": ["content-type", "x-amz-*"],
    "ExposeHeaders": ["ETag"],
    "MaxAgeSeconds": 3000
  }
]

Slide-এর S3 উদাহরণে একটা website bucket (my-bucket-html) থেকে আসা page আরেকটা website bucket (my-bucket-assets) থেকে coffee.jpg আনে। দ্বিতীয় bucket-কে origin হিসেবে প্রথম bucket-এর website endpoint http://my-bucket-html.s3-website.us-west-2.amazonaws.com-কে অনুমতি দিতে হবে, অথবা *-কে। http://-টা খেয়াল করো: https://-এর rule মিলবে না।

প্রতিটা মিল নিজে চেষ্টা করে দেখো, rule-এর ক্রমের ফাঁদ আর CORS পার হয়েও bucket policy আটকে দেওয়ার ঘটনা সহ:

CORS preflight simulator

একটা page-এর script S3 REST endpoint দিয়ে assets-example bucket-কে call করছে। Bucket-এর CORS configuration আর request বেছে নাও, তারপর পাঠিয়ে দেখো browser আর S3 কী করে।

assets-example-এর CORS configuration

[
 {
  "AllowedOrigins": [
   "https://www.example.com"
  ],
  "AllowedMethods": [
   "GET",
   "HEAD"
  ],
  "AllowedHeaders": []
 }
]

Method

বাড়তি request header

কী হয় দেখতে “Request পাঠাও” চাপো।

#CORS-এর যে ভুলগুলো পুরো বিকেল খেয়ে ফেলে

  • Rule ভুল bucket-এ। এটা থাকতে হবে সেই bucket-এ, যেখান থেকে browser আনছে।
  • Origin-এ scheme, port বা www-এর তফাত। http:// বনাম https://, :8443, example.com বনাম www.example.com, সবই আলাদা origin। Rule-এ শেষে slash দিলে (https://www.example.com/) সেটাও কখনো মেলে না।
  • AllowedHeaders-এ একটা header বাদ পড়েছে। AWS SDK-র PUT পাঠায় content-type আর কয়েকটা x-amz-* header; এগুলো list করো বা * দাও।
  • Script ETag পড়তে পারছে না। ওটা ExposeHeaders-এ যোগ করো।
  • CDN ভুল উত্তর cache করেছে। সামনে CloudFront থাকলে Origin, Access-Control-Request-Method আর Access-Control-Request-Headers header forward করো আর cache key-তে Origin রাখো (managed CORS-S3Origin origin request policy এটাই করে), OPTIONS method-ও অনুমতি দাও; নইলে এক origin-এর cache করা response, CORS header ছাড়া, অন্য origin-এর কাছে চলে যায়। এই কারণেই S3 Vary: Origin জুড়ে দেয়।
  • ভাবা যে CORS data রক্ষা করে। করে না: browser ছাড়াই যে কেউ S3-কে call করতে পারে। Data রক্ষা করো policy দিয়ে; CORS ব্যবহার করো শুধু নিজের page-কে পড়তে দেওয়ার জন্য।

#System design-এ static site

  • Serverless web front end। Static file S3-এ, OAC, HTTPS আর নিজের domain সহ CloudFront দিয়ে দেওয়া; dynamic call browser থেকে যায় API Gateway আর Lambda-তে, বা একটা ALB-তে। Patch করার মতো web server নেই, আর যেকোনো ভিড়ে নিজেই বাড়ে। Chapter ১৫-এর বেশিরভাগই এই কাঠামোর ওপর দাঁড়ানো।
  • Page আর ভারী asset আলাদা করা। Page মূল domain-এ, বড় media আলাদা bucket-এ বা আলাদা path-এর পেছনে। Origin আলাদা হলে asset bucket-এ CORS দাও; একটা CloudFront distribution আর path behaviour (/assets/*) দিয়ে করলে সবই same-origin, CORS লাগেই না।
  • Browser থেকে সরাসরি upload। Browser একটা pre-signed URL দিয়ে file সরাসরি S3-এ PUT করে (লেসন ১০.১২), তোমার server-কে byte-গুলো ছুঁতেই হয় না। এর জন্য PUT-এর CORS rule লাগে (আর multipart হলে ETag expose করা)।
  • Maintenance বা failover page। Route 53 failover policy-র secondary record হিসেবে ছোট্ট একটা S3 website (লেসন ৮.৩) মূল site বন্ধ থাকলে “একটু পরেই ফিরছি” দেখায়।
  • সীমাগুলো জানো। শুধু HTTP, শুধু public, server-side code নেই, Regional (CloudFront cache না করলে একটা Region-এ একটাই কপি)। এর কোনোটা লাগলে সামনে CloudFront বসাও, বা অন্য service বেছে নাও।

#নিজে করে দেখো

AWS CLI দিয়ে, এমন একটা folder-এ, যেখানে index.html, 404.html আর site-এর বাকি file আছে:

# 1. Bucket বানাও আর site upload করো (content type extension দেখে আন্দাজ হয়)
aws s3 mb s3://mishti-menu-example --region ap-south-1
aws s3 sync ./site s3://mishti-menu-example/

# 2. Index আর error document সহ website hosting চালু করো
aws s3 website s3://mishti-menu-example/ --index-document index.html --error-document 404.html

# 3. সবার জন্য read: এই bucket-এর public access খোলো, তারপর policy দাও
aws s3api put-public-access-block --bucket mishti-menu-example \
  --public-access-block-configuration BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=false,RestrictPublicBuckets=false
aws s3api put-bucket-policy --bucket mishti-menu-example --policy file://public-read.json

# 4. পরীক্ষা করো (ap-south-1-এর জন্য dot-Region endpoint)
curl -I http://mishti-menu-example.s3-website.ap-south-1.amazonaws.com/
curl -I http://mishti-menu-example.s3-website.ap-south-1.amazonaws.com/menu       # 302 → /menu/
curl -I http://mishti-menu-example.s3-website.ap-south-1.amazonaws.com/nothing    # 404
curl -I https://mishti-menu-example.s3.ap-south-1.amazonaws.com/                 # REST: 403 (list)

# 5. Routing rule আর একটা object redirect
aws s3api put-bucket-website --bucket mishti-menu-example --website-configuration file://website.json
aws s3api copy-object --bucket mishti-menu-example --copy-source mishti-menu-example/old-menu.html \
  --key old-menu.html --website-redirect-location /menu/ --metadata-directive REPLACE
aws s3api get-bucket-website --bucket mishti-menu-example

৩ নম্বর ধাপে ACL-এর দুটো setting চালুই থাকে (ACL তো ব্যবহারই হচ্ছে না), বন্ধ হয় শুধু public policy আটকানোর দুটো। website.json-এ index, error আর routing rule একসাথে থাকে:

{
  "IndexDocument": { "Suffix": "index.html" },
  "ErrorDocument": { "Key": "404.html" },
  "RoutingRules": [
    { "Condition": { "KeyPrefixEquals": "docs/" }, "Redirect": { "ReplaceKeyPrefixWith": "documents/" } }
  ]
}

Asset bucket-এ CORS, তারপর হাতে একটা preflight (curl browser সেজে browser-এর header-গুলো পাঠায়):

aws s3api put-bucket-cors --bucket assets-example --cors-configuration file://cors.json
aws s3api get-bucket-cors --bucket assets-example

curl -i -X OPTIONS https://assets-example.s3.ap-south-1.amazonaws.com/photos/roshogolla.jpg \
  -H "Origin: https://admin.example.com" \
  -H "Access-Control-Request-Method: PUT" \
  -H "Access-Control-Request-Headers: content-type"
# কোনো rule মিললে Access-Control-Allow-Origin সহ 200; না মিললে 403

aws s3api delete-bucket-cors --bucket assets-example

API-র জন্য file-টা একটা মোড়কে রাখতে হয়: {"CORSRules": [ …rule-গুলো… ]}; console নেয় শুধু list-টা।

#সারসংক্ষেপ

মূল কথা

  • S3 static site চালায় (HTML, CSS, JavaScript, ছবি, React বা Angular দিয়ে বানানো SPA), কোনো server ছাড়াই; server-side code (PHP, Node.js, Python) চালাতে পারে না। Hosting নিজে বিনা পয়সার: দাম দাও storage, request আর transfer out-এর।
  • সাজানো: hosting চালু → index document (বাধ্যতামূলক) আর error document (ঐচ্ছিক, শুধু 4xx) → Block Public Access বন্ধ (bucket আর account) → arn:aws:s3:::bucket/*-এ public s3:GetObject দেওয়া bucket policy।
  • Endpoint: http://bucket.s3-website-region.amazonaws.com (পুরোনো Region) বা http://bucket.s3-website.region.amazonaws.com (বাকিগুলো)। শুধু HTTP।
  • 403 Forbidden → সবার পড়ার মতো না: Block Public Access, policy নেই বা ভুল, অন্য account-এর object, SSE-KMS, Requester Pays, স্পষ্ট একটা Deny। 404 → এমন key নেই বা index document নেই। 302 → শেষের slash ছাড়া folder।
  • Website endpoint বনাম REST endpoint: শুধু public বনাম public আর private, শুধু GET/HEAD বনাম সব operation, index document, HTML বনাম XML error, redirect, HTTPS নেই বনাম HTTPS আছে।
  • Redirect: পুরো bucket অন্য host-এ (www → apex), routing rule (prefix বা error code, সর্বোচ্চ ৫০টা), বা প্রতিটা object-এ x-amz-website-redirect-location।
  • নিজের domain: domain-এর হুবহু নামে bucket + Route 53 Alias। HTTPS: CloudFront (সবচেয়ে ভালো private bucket আর OAC দিয়ে), বা Amplify Hosting।
  • Origin = scheme + host + port। Browser same-origin policy মানায়; অন্য origin-এর CORS header সেটা শিথিল করে। Simple request-এ থাকে Origin; বাকিগুলোর আগে OPTIONS দিয়ে preflight হয়।
  • S3-এর CORS configuration যায় resource দেওয়া bucket-এ: JSON rule, AllowedOrigins (একটা *, বা শুধু *), AllowedMethods (GET, PUT, POST, DELETE, HEAD), AllowedHeaders, ExposeHeaders, MaxAgeSeconds; সর্বোচ্চ ১০০টা rule, প্রথম মিলটাই জেতে। CORS কখনো access দেয় না।