HTMLタグで属性が重複したときの挙動について

  • このエントリーをはてなブックマークに追加

次のタグのように属性が重複している場合、パーサーはどのように処理されるのだろうか。
もともと、Nokogiriを使ってパースしていて属性が取れないぞとなって調べたのでメモ。

<div id="s" class="a" class="b"></div>

結論から言うと、先に出現した属性が優先され、後続の重複した属性は無視されます。
Firefox, Chrome, Gumbo-parser, Nokogiri(sax-parser)などで確認しました。

仕様

WHATWGの仕様はこちらにあります。

There must never be two or more attributes on the same start tag whose names are an ASCII case-insensitive match for each other.
https://html.spec.whatwg.org/multipage/syntax.html

仕様的に、2回以上含めることはパースエラーとなる。

This error occurs if the parser encounters an attribute in a tag that already has an attribute with the same name. The parser ignores all such duplicate occurrences of the attribute.
https://html.spec.whatwg.org/#parse-error-duplicate-attribute

しかし、現実的にパースエラーにすると色々と困るので、エラーとして停止する/しないを選択することができ、殆どのブラウザはHTML(not XMLを解釈する時には、重複属性の時は最初の値を採用し、後続に出現した重複属性は無視をする、という選択をしているようです。

参考

Vue3でAタグをクリックした時に遷移させないで自前の処理をする

  • このエントリーをはてなブックマークに追加

Vue3にて、例えば、AタグをButton代わりに使う場合、こんな感じで書きたいわけですが、HashベースのRouterを使っている場合、#で遷移が発生してしまいます。

<a href="#" @click="linkClick" class="btn btn-outline-primary">link</a>

なのでこう書きます。

<a href="#" @click.prevent.stop="linkClick" class="btn btn-outline-primary">link</a>

stopでclickイベントの伝播が止まり、preventで既定のアクションを処理しない。
例えばformのsubmitをさせない場合はpreventを指定する(<form @submit.prevent="mySubmit">)

参考

Vue3でBootstrapとBootstrap Iconを利用する

  • このエントリーをはてなブックマークに追加
npm install --save bootstrap bootstrap-icons

bootstrapをcustomする

main.js
import 'bootstrap'
import '@/scss/custom.scss'

bootstrapの変数を上書きしてからbootstrapのscssをimportすると反映される。

scss/custom.scss
$body-bg: #000;
@import "node_modules/bootstrap/scss/bootstrap";

bootstrap iconsを利用する

main.js
import 'bootstrap-icons/font/bootstrap-icons.css'

iconsのcssをimportしてから、

App.vue
<i class="bi bi-file-bar-graph"></i>

参考

Ruby標準のLoggerでスレッドIDを出力する

  • このエントリーをはてなブックマークに追加

その1 / procを指定する

formatterにprocを直接指定する。
色々と制限があります。あまりオススメしません。


def main(logger)
logger.info("main"){ "こんにちは" }
begin
raise "エラー"
rescue => err
logger.error("main"){ err }
end
end

custom = Logger.new(STDOUT, formatter: proc { |severity, time, progname, msg|
# msgが例外の場合、originalは表示されるけど、これはされない
"%s, [%s#%d/#%d] %5s -- %s: %s\n" % [severity[0..0], time.strftime("%Y-%m-%dT%H:%M:%S.%6N "), Process.pid, Thread.current.object_id, severity, progname, msg]
})
main(custom)
I, [2021-07-11T10:36:43.688551 #4163]  INFO -- main: こんにちは
E, [2021-07-11T10:36:43.688635 #4163] ERROR -- main: エラー (RuntimeError)
app.rb:7:in `main'
app.rb:14:in `<main>'

その2 / Logger::Formatterを継承する

Logger::Formatterを継承して自分用のFormatterを作り、Loggerのfometterに指定します。
この方法は直接Procを指定するわけではないので、標準クラスが持っているメソッドであるformat_datetimemsg2strが使えます。


def main(logger)
logger.info("main"){ "こんにちは" }
begin
raise "エラー"
rescue => err
logger.error("main"){ err }
end
end

class CustomFormatter < Logger::Formatter
CustomFormat = "%s, [%s#%d/#%d] %5s -- %s: %s\n".freeze
def initialize
super
end
def call(severity, time, progname, msg)
CustomFormat % [severity[0..0], format_datetime(time), Process.pid, Thread.current.object_id, severity, progname, msg2str(msg)]
end
end
custom2 = Logger.new(STDOUT, formatter: CustomFormatter.new)
main(custom2)

I, [2021-07-11T10:36:43.688747 #4163/#70368626219440]  INFO -- main: こんにちは
E, [2021-07-11T10:36:43.688773 #4163/#70368626219440] ERROR -- main: エラー (RuntimeError)
app.rb:7:in `main'
app.rb:32:in `<main>'

参考

vueでscssを利用する

  • このエントリーをはてなブックマークに追加

vue-cliのバージョンは4.5.13を想定。
vue createした時に「Manually select features」を選択して「CSS Pre-processors」を選択すれば最初から入るが、「Default」を選択した場合は、babeleslintが有効になるだけである。

vue --version
@vue/cli 4.5.13

後から入れる場合は、sasssass-loaderをdevDependenciesに追加する。ただし、sass-loaderは現時点でバージョンが12.1.0であり、webpackの依存バージョンが競合してエラーになってしまう。
回避するためにはバージョンを指定してインストールする。それぞれvue-cliで最初に有効にした場合にインストールされるバージョンを指定する。
これらのバージョンは、ここで参照する事ができます。

npm install --save-dev sass@1.26.5
npm install --save-dev sass-loader@8.0.2

上記では8.0.2を指定していますが、vue-cliが4.5.13で作ったpackage.jsonに対しては、sass-loader@10.2.0まではインストール可能です。

sass-loaderのpeerDependenciesが、10.2.0では次の指定に対して、

10.2.0
"webpack": "^4.36.0 || ^5.0.0"

11.0.0からは、次のようになっているからです。

11.0.0
"webpack": "^5.0.0"

@vue/cli-plugin-babelのバージョンは4.5.13で、そのdependenciesに"webpack": "^4.0.0"とあり、4.46.0が実際にインストールされています。

書き方

vueファイルのstyleにlang="scss"と書く。

サンプル
<style lang="scss">
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
margin-top: 60px;
}
body {
color: blue;
p {
font-weight: bold;
}
}
</style>

参考

Vue3で環境変数の設定をする

  • このエントリーをはてなブックマークに追加

Vue2とVue3では設定方法が異なります。この記事ではVue3に関する設定方法を。
npm run serve mode=mockとして起動することで、.env.mockのファイルを読み込みます。開発環境の時はNODE_ENVdevelopmentなので、.env.developmentが読み込まれます。

環境変数名はVUE_APP_で始まる必要があり、それ以外は無視されてしまう。

.env.development
VUE_APP_HOGEFUGA=true

process.env.VUE_APP_HOGEFUGAで参照可能。

.envは全てのケースにおいて読み込まれますが、優先順位があり、.env.developmentが先に読み込まれる。また、.env.local.env.development.localのように末尾に.localのファイルがあれば、それが優先的に読み込まれる。.localは各個人の環境でのみ利用することを想定しているファイルで.gitignoreの対象となっている。

例えば、--mode=developmentで起動した場合、.env.development.local -> .env.development -> .env.local -> .env の順番に読み込みされて、同じ環境変数名がある場合は先勝ちである。

これらの処理はdotenvdotenv-expandのモジュールがやってるのだと思うのですが、そんなコードは見つけられず、dotenv-flowにこのような処理があるけど、vue3からはリンクはされておらず。

どうやら自前で処理している様子。
vue-cliのcli-serviceのService.jsに処理がありました。
dotenvを利用しており、環境変数DEBUG=trueをつけて実行すると、dotenvのデバッグログが有効になって、そのあたりのログが出力されます。

参考

Vue3でWebページを表示しようとしたらInvalid Host Headerと表示される

  • このエントリーをはてなブックマークに追加

例えば、 example.local127.0.0.1にマッピングして、example.localでアクセスした時にInvalid Host Headerと表示される。

vue.config.jsで次の設定を入れる

module.exports = {
// options...
devServer: {
disableHostCheck: true
}
}

参考

rubyのhttpclientでhttpsからhttpにredirectするとエラーになる

  • このエントリーをはてなブックマークに追加

httpclientでredirectの自動追従は次のオプションで出来る。

client = HTTPClient.new()
client.get('https://nghttp2.org/httpbin/redirect/2', :follow_redirect => true)

httpbinのredirectが404になるので、代わりのサイトで検証。

しかし、httpsからhttpへのredirectはBadResponseErrorになります。

client = HTTPClient.new()
client.get('https://nghttp2.org/httpbin/redirect-to?url=http%3A%2F%2Fexample.com', :follow_redirect => true)

ライブラリのコードはこんな感じでif https?(uri) && !https?(newuri)このコードで弾かれています。

def default_redirect_uri_callback(uri, res)
newuri = urify(res.header['location'][0])
if !http?(newuri) && !https?(newuri)
warn("#{newuri}: a relative URI in location header which is not recommended")
warn("'The field value consists of a single absolute URI' in HTTP spec")
newuri = uri + newuri
end
if https?(uri) && !https?(newuri)
raise BadResponseError.new("redirecting to non-https resource")
end
puts "redirect to: #{newuri}" if $DEBUG
newuri
end
https://github.com/nahi/httpclient/blob/master/lib/httpclient.rb#L722-L734

HTTPClient::BadResponseError (redirecting to non-https resource)

これはこれで正しいのですが、クローラーを書くときは無視したいことが多いです。そんな時は、clientのredirect_uri_callbackを自前のロジックに変更します。

client = HTTPClient.new()
client.redirect_uri_callback = lambda {|uri, res|
newuri = HTTPClient::Util.urify(res.header['location'][0])
if (newuri.scheme && (newuri.scheme.downcase == "https" || newuri.scheme.downcase == "http"))
else
newuri = uri + newuri
end
newuri
}

参考情報

rubyのhttpclientでgzip対応

  • このエントリーをはてなブックマークに追加

httpclientでgzipのresponseを自動で処理するためのオプション。

client = HTTPClient.new()
client.transparent_gzip_decompression = true

参考情報

PlantUML-ServerをBasic認証付きで公開する

  • このエントリーをはてなブックマークに追加

plantuml-serverはdocker imageが既にあるので簡単に公開する事ができます。
外部サーバに立てると、誰でもアクセスできてしまうため、Basic認証を付けたいことがあります。WebのUIから使うだけならIDP連携でOAuth認証を入れてしまえば良いのですが、Visual Studio Codeのプラグインと連携させる場合は、OAuthは対応していないため、Basic認証にするしかありません。

docker-composeを利用して、nginxでbasic認証をかけてplantumlにproxyするのが簡単です。
そして、nginxのbasic認証に特化したイメージがあります ⇒ quay.io/dtan4/nginx-basic-auth-proxy

しかし、普通に設定すると、PlantUMLの画面でホスト名がplantuml:8080というホスト名になってしまいます。
これは、例えば、http://example.comにデプロイしたとして、Userhttp://example.comnginxhttp://plantuml:8080/PlantUMLのApplicationサーバ(Jetty or Tomcat) となり、PlantUML(Jetty)から見ると、plantuml:8080というホスト名でアクセスに来ているからです。

PlantUMLのコード

PlantUMLのコードでは、JSPのTagLibで次のように記述されています。

<c:set var="contextroot" value="${pageContext.request.contextPath}" />
<c:if test="${(pageContext.request.scheme == 'http' && pageContext.request.serverPort != 80) ||
(pageContext.request.scheme == 'https' && pageContext.request.serverPort != 443) }">
<c:set var="port" value=":${pageContext.request.serverPort}" />
</c:if>
<c:set var="scheme" value="${(not empty header['x-forwarded-proto']) ? header['x-forwarded-proto'] : pageContext.request.scheme}" />
<c:set var="hostpath" value="${scheme}://${pageContext.request.serverName}${port}${contextroot}" />
<c:if test="${!empty encoded}">
<c:set var="imgurl" value="${hostpath}/png/${encoded}" />
<c:set var="svgurl" value="${hostpath}/svg/${encoded}" />
<c:set var="txturl" value="${hostpath}/txt/${encoded}" />
<c:if test="${!empty mapneeded}">
<c:set var="mapurl" value="${hostpath}/map/${encoded}" />
</c:if>
</c:if>

<a href="${svgurl}">View as SVG</a>

pageContext.request.serverNameplantuml:8080になります。
一方、nginx-basic-auth-proxyでは、X-Forwarded-Hostを設定しています。(参考)

つまり、Jetty側でこのヘッダをHostとして扱えば良いわけです。
Jettyでは、X-Forwarded-xxの処理を良い感じに処理してくれるモジュールとしてhttp-forwardedがあります。

PlantUMLのJetty版の元のイメージはこちらを利用しており、
http-forwardedはデフォルトでは有効になっていませんが、/usr/local/jetty/etc/jetty-http-forwarded.xmlにあります。
そして、有効にするためには、--module=http-forwardedの引数を付ければ有効になります。

PlantUMLのDockerfileの定義は、Entrypointが/docker-entrypoint.sh。Commandがjava -jar /usr/local/jetty/start.jarです。
ここに引数を追加するには、command: java -jar /usr/local/jetty/start.jar --module=http-forwardedとすれば良いです。

docker-compose.yaml

こんな感じで。
ALLOW_PLANTUML_INCLUDEは、!includeで綺麗にするスタイルを外部ファイル化しており、毎回当てるので有効にしています。

version: "3.3"
services:
nginx-proxy:
image: quay.io/dtan4/nginx-basic-auth-proxy
ports:
- 80:80
environment:
BASIC_AUTH_USERNAME: test-user
BASIC_AUTH_PASSWORD: test-password
PROXY_PASS: http://plantuml:8080/
SERVER_NAME: proxy.internal
networks:
- container-link
plantuml:
image: plantuml/plantuml-server:jetty
command:
- "java"
- "-jar"
- "/usr/local/jetty/start.jar"
- "--module=http-forwarded"
environment:
- ALLOW_PLANTUML_INCLUDE=true
networks:
- container-link
networks:
container-link:

VS Codeのプラグインの設定

利用しているPlantUMLのPluginはこちら。

名前: PlantUML
ID: jebbs.plantuml
説明: Rich PlantUML support for Visual Studio Code.
バージョン: 2.14.5
パブリッシャー: jebbs
VS Marketplace リンク: https://marketplace.visualstudio.com/items?itemName=jebbs.plantuml

次のように、RenderをLocalからPlantUMLServerにします。
そして、Serverにdocker-composeで起動したURLを設定します。Basic認証の情報はURLに入れる事ができるので、http://test-user:test-password@example.com/のように設定します。

参考