LoginSignup
7
7

More than 5 years have passed since last update.

AWS LambdaでWebページをギャル文字にしてみた

Last updated at Posted at 2016-08-09

前回の続き

AWS LamdbaとAPI GatewayでWebの中間層が簡単に作れることがわかったのでちょっと遊んでみることにした
言語は今回もScalaで実装

今回の仕様

ブラウザからプロキシっぽいAPI Gateway経由で任意のWebページのURLにアクセスすると内容をギャル文字に変換して表示する

galogo-filter(url) -> ギャル文字化されたHTMLページ

ギャル文字とは

galmoji("こんにちは") == "〓ω(ニ干l£"

ちょっと昔に流行ったらしい
詳細はWikipedia参照
意外と深い
面倒なのでひらがなだけやる

単純なプロキシの作成

proxy(url)

渡されたurlをGETしてそのまま返すとどうなるか

src/main/scala/filter/HtmlFilter.scala
package filter

trait HtmlFilterBase {
  import io.Source
  // テスト時はオーバーライドできるように(ファイルからとかプロキシ使うときとか)
  def fromURL(url: String, encode: String = "UTF-8") = Source.fromURL(url, encode)

  def proxy(url: String) = {
    fromURL(url).mkString
  }
}

class HtmlFilter extends HtmlFilterBase

注 : ラムダ関数の最終引数のcom.amazonaws.services.lambda.runtime.Contextは省いてもよいようなので今回から省いた。要るときだけつける

上記コードをAWS Lambdaにデプロイし、APIGatewayにて公開

image

前回と同様の上記修正をしてprodに再デプロイ

ブラウザから前回記事のURLをターゲットにアクセスしてみる

https://hogehoge.execute-api.ap-northeast-1.amazonaws.com/prod/proxy?url=http://qiita.com/bakenezumi/items/4026bbd4117be598a251
(hogehogeは内緒)

image

失敗

image

Content-Typeが application/jsonになってる
今までさわってなかった「メソッドレスポンス」でhttpヘッダーをいじれるようなので修正する

image

再デプロイ後

image

text/htmlにはなったけどくずれた
ですよね..

他リソースへの参照の相対パスを絶対パスに変換

absolute-filter(url)
画像、js、cssといった他リソースへの参照方法には絶対参照と相対参照の2つの方法があるが、

  • 絶対参照: <link href="http:/hogehoge.com/fugafuga.css" />
  • 相対参照: <link href="/fugafuga.css" />

相対参照の場合今回のフィルタを通すとホストがAWSのAPIGatewayになるため、あたりまえだが見つからない
なので全てのリンクを絶対参照に直してやる必要がある

正規表現を駆使して頑張ろうかとも思ったが、
「Jsoup」というJavaのライブラリがうまい具合にやってくれるということをGoogle先生が教えてくれたのでそれを使った

src/main/scala/filter/HtmlFilter.scala(追加)
  def absoluteFilter(url: String) = {
    import scala.collection.JavaConverters._
    import org.jsoup.Jsoup
    val doc = Jsoup.connect(url).get
    for (e <- doc.select("link").asScala) e.attr("href", e.absUrl("href"))
    for (e <- doc.select("script").asScala) e.attr("src", e.absUrl("src"))
    for (e <- doc.select("a").asScala) e.attr("href", e.absUrl("href"))
    for (e <- doc.select("img").asScala) e.attr("src", e.absUrl("src"))
    doc.toString
  }

Scalaである必要がないという突っ込みは無しで
Lambda上げてAPI Gatewayとつないでさっきと同じ設定をしてデプロイ

https://hogehoge.execute-api.ap-northeast-1.amazonaws.com/prod/absolute-filter?url=http://qiita.com/bakenezumi/items/4026bbd4117be598a251

image

ファビコンはでたけど内容は変わらず
まだ足りない
なんかChromeが怒ってる
曰く

Mixed Content: The page at 'https://hogehoge.execute-api.ap-northeast-1.amazonaws.com/prod/proxy?url=http://qiita.com/bakenezumi/items/4026bbd4117be598a251' was loaded over HTTPS, but requested an insecure stylesheet 'http://cdn.qiita.com/assets/application-e985a098a72f1b0e009b75feaa8a3df6df3339d3a8b80b7b6d518b66da3c5b44.css'. This request has been blocked; the content must be served over HTTPS.

httpsのサイトからhttpのサイトへのスクリプト、及びスタイルシートのリクエストは危険だからできないとのこと
そりゃそうだ

クロスサイト対応

absolute-filter(url)の修正
めげずにがんばった

src/main/scala/filter/HtmlFilter.scala(修正)
  val proxyUrl = "./proxy?url="
  def absoluteFilter(url: String) = {
    import scala.collection.JavaConverters._
    import org.jsoup.Jsoup

    val doc = Jsoup.connect(url).get
    // クロスサイト回避のためにproxyを介す
    for (e <- doc.select("link").asScala) if (e.attr("href") != "") e.attr("href", proxyUrl + e.absUrl("href"))
    // クロスサイト回避のためにproxyを介す
    for (e <- doc.select("script").asScala) if (e.attr("src") != "") e.attr("src", proxyUrl + e.absUrl("src"))
    for (e <- doc.select("a").asScala) e.attr("href", proxyUrl + e.absUrl("href"))
    for (e <- doc.select("img").asScala) e.attr("src", e.absUrl("src"))
    doc.toString
  }

ようは、他リソースへの絶対リンクに当記事最初のproxyラムダをかますことで無理やり同一サイトとみなさせるようにした
<link href="./proxy?url=http:/hogehoge.com/fugafuga.css" />
proxyとこのabsolute-filterは同一のAPI nameに配置しなければならないことに注意
異なるAPI nameだとhost名が変わる
んで、再表示

結果、、エラーは減ったけど出力変わらず、、(画像省略)
一部のcssとjsの内容が、

{errorMessage=Input length = 1, errorType=java.nio.charset.MalformedInputException, stackTrace=["java.nio.charset.CoderResult.throwException(CoderResult.java:281)","sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:339)","sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)","java.io.InputStreamReader.read(InputStreamReader.java:184)","java.io.BufferedReader.read1(BufferedReader.java:210)","java.io.BufferedReader.read(BufferedReader.java:286)","java.io.Reader.read(Reader.java:140)","scala.io.BufferedSource.mkString(BufferedSource.scala:96)","filter.HtmlFilterBase$class.proxy(HtmlFilter.scala:9)","filter.HtmlFilter.proxy(HtmlFilter.scala:14)","sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)","sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)","sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)","java.lang.reflect.Method.invoke(Method.java:498)"]}

こんなラムダからのエラースタックトレースJSONが返ってきてることがわかる

proxyのgzip対応

proxy(url)の(修正)

エラーになったjsの実コンテンツを確認するとgzip化されてた
なので解凍する
Encodingの見方がよくわからなかったので失敗したら解凍するようにした
解凍の仕方はxuweiさんがメモってた

src/main/scala/filter/HtmlFilter.scala(修正)
  def fromURL(url: String, encode: String = "UTF-8") = Source.fromURL(url, encode)
  def fromGzip(url: String, encode:String = "UTF-8" ) = {
    import java.net.URL
    import java.io.BufferedInputStream
    import java.util.zip.GZIPInputStream
    Source.fromInputStream(
      new GZIPInputStream(
        new BufferedInputStream(
           new URL(url).openStream()
        )
      ), encode)
  }
  def proxy(url: String) = {
    try {
      fromURL(url).mkString
    } catch {
      case _: java.nio.charset.MalformedInputException => fromGzip(url).mkString
      case e => throw e
    }
  }

どきどきしながら再表示

image

ロゴがでた!
ちがう、そうじゃない

CSS用のプロキシ準備

css-proxy(url)の追加
absolute-filter(url)の修正

CSSはどうもContent-Typeをtext/cssで返さないといけないようなのでそれようのAPIGateway「css-proxy」を追加し(Lamdaはproxyをそのまま利用)、それに合わせabsolute-filterのLambdaを修正

src/main/scala/filter/HtmlFilter.scala(修正)
  val proxyUrl = "./proxy?url="
  val cssProxyUrl = "./css-proxy?url="
  def absoluteFilter(url: String) = {
    import scala.collection.JavaConverters._
    import org.jsoup.Jsoup

    val doc = Jsoup.connect(url).get
    // クロスサイト回避のためにproxyを介す
    for (e <- doc.select("link").asScala) if (e.attr("href") != "") e.attr("href", cssProxyUrl + e.absUrl("href"))
    // クロスサイト回避のためにproxyを介す
    for (e <- doc.select("script").asScala) if (e.attr("src") != "") e.attr("src", proxyUrl + e.absUrl("src"))
    for (e <- doc.select("a").asScala) e.attr("href", e.absUrl("href"))
    for (e <- doc.select("img").asScala) e.attr("src", e.absUrl("src"))
    doc.toString
  }

再表示

image

やっと出た!!
ふー、、疲れた

ギャル文字変換

galgo-filter(url)

ここまでできたらギャル文字への変換は簡単だった

src/main/scala/filter/HtmlFilter.scala(追加)
  val galMap = Map(
    ('あ' -> "ぁ"), ('い' -> "レヽ"), ('う' -> "ぅ"), ('え' -> "ぇ"), ('お' -> "ぉ"),
    ...
  )

  val galgoUrl = "./galgo-filter?url="

  def galgoFilter(url: String) = {
    import scala.collection.JavaConverters._
    import org.jsoup.Jsoup

    val doc = Jsoup.connect(url).get
    // クロスサイト回避のためにproxyを介す
    for (e <- doc.select("link").asScala) if (e.attr("href") != "") e.attr("href", cssProxyUrl + e.absUrl("href"))
    // クロスサイト回避のためにproxyを介す
    for (e <- doc.select("script").asScala) if (e.attr("src") != "") e.attr("src", proxyUrl + e.absUrl("src"))
    // リンク先もギャル語(似非再帰 モナドっぽい)
    for (e <- doc.select("a").asScala) e.attr("href", galgoUrl + e.absUrl("href"))
    // イメージは遅そうだからそのまま
    for (e <- doc.select("img").asScala) e.attr("src", e.absUrl("src"))
    doc.toString.flatMap(c =>  galMap.getOrElse(c, c.toString))
  }

Lambda作ってAPIGateway設定&公開
https://hogehoge.execute-api.ap-northeast-1.amazonaws.com/prod/galgo-filter?url=http://qiita.com/bakenezumi/items/4026bbd4117be598a251
にアクセス!

image

読めないけど成功したっぽい!

ギャル文字化されたページのリンクをたどるとそこから先もギャル文字

image

コード7行目のコメントに注目

今回のコード

所感

Webセキュリティについて考えさせられた
変なサイトから不用意にメジャーサイトのリンク先に飛ぶのは危険
URLは絶対確認しようね!

あとそろそろ手作業がきついので自動化したい

蛇足

前回のjson変換との組み合わせもいけた

https://hogehoge.execute-api.ap-northeast-1.amazonaws.com/prod/htmlToJson?url=https://hogehoge.execute-api.ap-northeast-1.amazonaws.com/prod/galgo-filter?url=http://qiita.com/bakenezumi/items/4026bbd4117be598a251

image

7
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
7