AWSの部屋

AWS学習者向けのブログです

S3 で静的Webサイトをホスティングしてみる

はじめに

筆者はAWS認定ソリューションアーキテクト-プロフェッショナル合格に向けて勉強中ですが、座学はあまり性に合わずできるだけ手を動かして勉強したいため、本ブログを通じて AWS の様々なサービスを使ってみようと思っています。本日は S3 で静的Webサイトをホスティングしてみようと思います。

S3 とは

Amazon Simple Storage Service (Amazon S3) は、業界をリードするスケーラビリティ、データ可用性、セキュリティ、およびパフォーマンスを提供するオブジェクトストレージサービスです。(出典:Amazon S3とは

今回作ってみる Webサイト

以下をサンプルとして使ってみようと思います。

developers.arcgis.com

ArcGIS に関しては筆者の別ブログをご参照ください。

構成

手順

  1. S3にバケットを作成
  2. バケットポリシーを編集
  3. 静的ウェブサイトホスティングを有効にする
  4. index.html を作成
  5. index.html をバケットにアップロード
  6. 動作確認

1.S3にバケットを作成

以下のバケットを作成します。

「パブリックアクセスをすべてブロック」のチェックを外してバケットを作成してください。

2.バケットポリシーを編集

バケットポリシーを以下のように編集します。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "PublicReadForGetObjects",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::バケット名/*"
        }
    ]
}

3.静的ウェブサイトホスティングを有効にする

バケットのプロパティから静的ウェブサイトホスティングを有効にします。

4.index.html を作成

以下のように index.html を作成します。

<html>
  <head>
    <meta charset="utf-8" />
    <meta
      name="viewport"
      content="initial-scale=1,maximum-scale=1,user-scalable=no"
    />
    <title>
      Highlight feature with effects and blending | Sample | ArcGIS API for
      JavaScript 4.22
    </title>
    <style>
      html,
      body,
      #viewDiv {
        padding: 0;
        margin: 0;
        height: 100%;
        width: 100%;
      }
      #messageDiv {
        padding-left: 10px;
        padding-right: 10px;
      }
    </style>

    <link
      rel="stylesheet"
      href="https://js.arcgis.com/4.22/esri/themes/light/main.css"
    />
    <script src="https://js.arcgis.com/4.22/"></script>

    <script>
      require([
        "esri/WebMap",
        "esri/views/MapView",
        "esri/layers/VectorTileLayer",
        "esri/Graphic",
        "esri/layers/FeatureLayer",
        "esri/layers/GraphicsLayer",
        "esri/layers/GroupLayer"
      ], (
        WebMap,
        MapView,
        VectorTileLayer,
        Graphic,
        FeatureLayer,
        GraphicsLayer,
        GroupLayer
      ) => {
        const map = new WebMap({
          basemap: {
            portalItem: {
              id: "f35ef07c9ed24020aadd65c8a65d3754" // modern antique vector tiles
            }
          }
        });
        const vtLayer = new VectorTileLayer({
          portalItem: {
            id: "2afe5b807fa74006be6363fd243ffb30" // gray vector tiles canvas
          }
        });
        const countries = new FeatureLayer({
          portalItem: {
            id: "53a1e68de7e4499cad77c80daba46a94" // boundaries of countries
          }
        });
        // This group layer groups the gray canvas vector tiles and
        // countries feature layer.
        // With destination-over blendMode, the background layer covers
        // the top layer. The top layer is put behind the destination layer.
        // So when the app starts, the basemap layer will be shown over this layer
        const groupLayer = new GroupLayer({
          layers: [vtLayer, countries],
          blendMode: "destination-over"
        });
        map.add(groupLayer);
        const view = new MapView({
          container: "viewDiv",
          map: map,
          zoom: 6,
          center: [2, 46],
          constraints: {
            snapToZoom: false,
            minScale: 147914381
          }
        });
        let layerView, animation;
        // countryGraphicsLayer is added to the view's basemap.
        // It will contain black polygon covering the extent of the world
        // the country graphic will also be added to this layer when user clicks a country.
        // With destination-in blend mode, the contents of background layer is
        // kept where it overlaps with top layer. Everything else is made transparent.
        // In this case, the countryGraphicsLayer will be displayed underneath
        // modern antique vector tiles basemap.
        // The bloom effect will add a glow around the selected country.
        const countryGraphicsLayer = new GraphicsLayer({
          blendMode: "destination-in",
          effect: "bloom(200%)"
        });
        map.loadAll().then(async () => {
          addWorld();
          map.basemap.baseLayers.getItemAt(1).blendMode = "multiply";
          // add the buffer graphicslayer to the basemap
          map.basemap.baseLayers.add(countryGraphicsLayer);
          // get a reference ot the countries featurelayer's layerview
          // layerview will be queried to get the intersecting country
          // when user clicks on the map
          layerView = await view.whenLayerView(countries);
        });
        view.ui.add("messageDiv", "top-right");
        const symbol = {
          type: "simple-fill",
          color: "white",
          outline: null
        };
        // listen to the view's click event
        view.on("click", async (event) => {
          // query the countries featurelayer for a country that intersects the point
          // user clicked on
          const {
            features: [feature]
          } = await layerView.queryFeatures({
            geometry: view.toMap(event),
            returnGeometry: true,
            maxAllowableOffset: 10000,
            outFields: ["*"]
          });
          countryGraphicsLayer.graphics.removeAll();
          animation && animation.remove();
          let world = addWorld();
          // add the clicked country feature to the graphicslayer
          if (feature) {
            feature.symbol = symbol;
            countryGraphicsLayer.graphics.add(feature);
            // add a fade animation to show the highlight effect
            // for the selected country
            animation = fadeWorld(world);
            // zoom to the highlighted country
            view.goTo(
              {
                target: view.toMap(event),
                extent: feature.geometry.extent.clone().expand(1.8)
              },
              { duration: 1000 }
            );
          }
        });
        function addWorld(world) {
          world = new Graphic({
            geometry: {
              type: "extent",
              xmin: -180,
              xmax: 180,
              ymin: -90,
              ymax: 90
            },
            symbol: {
              type: "simple-fill",
              color: "rgba(0, 0, 0, 1)",
              outline: null
            }
          });
          countryGraphicsLayer.graphics.add(world);
          return world;
        }
        // add a fading animation when user clicks on a country
        function fadeWorld(world) {
          let timer;
          // requestAnimationFrame method specifies "frame" function
          // to perform an animation where the opacity of the world polygon graphic
          // decreased by 0.1 until it is 0 or completely transparent
          // then the animation is cancelled
          function frame() {
            const symbol = world.symbol.clone();
            symbol.color.a = Math.max(0, symbol.color.a - 0.1);
            world.symbol = symbol;
            if (symbol.color.a > 0) {
              timer = requestAnimationFrame(frame);
            }
          }
          frame();
          return {
            remove() {
              cancelAnimationFrame(timer);
            }
          };
        }
      });
    </script>
  </head>
  <body>
    <div id="viewDiv"></div>
    <div id="messageDiv" class="esri-widget esri-heading">
      <h4 class="esri-heading">Click on a country</h4>
    </div>
  </body>
</html>

5.index.html をバケットにアップロード

index.html をバケットにアップロードします。

バケットのプロパティに Web サイトの URL があるのでこれを使って Web サイトにアクセスしてください。

6.動作確認

Web サイトにアクセスできることを確認しました。

さいごに

AWS認定試験でも頻繁に出題される S3 の Webサイトホスティングですが、ちょっとしたWebサイトを公開するのには非常に便利な機能ですね。今後はただ Web サイトを公開するだけではなくCloudFront などを使ってみようと思います。