2024-04-23 13:21:26 +00:00
|
|
|
+++
|
|
|
|
date = "2022-11-22"
|
|
|
|
tags = ["matrix","fly"]
|
|
|
|
title = "dendriteをupgradeしてみた"
|
|
|
|
slug = "matrix-dendrite-2"
|
|
|
|
+++
|
|
|
|
|
|
|
|
### matrix
|
|
|
|
|
|
|
|
micro-blogのmastodonがtwitterに似ているのなら、chatのmatrixはslackに似ています。
|
|
|
|
|
|
|
|
mastodon, matrixの類似点は分散であるところです。
|
|
|
|
|
|
|
|
matrixのaddressは`@user:example.com`となります。
|
|
|
|
|
|
|
|
matrixには様々なclientがあり、とくにwebのclientが手軽です。
|
|
|
|
|
|
|
|
例えば、elementがそれに当たります。自分が使っているserverを指定します。
|
|
|
|
|
|
|
|
https://app.element.io/
|
|
|
|
|
|
|
|
matrixは、serverからのapiとclientの組み合わせで使います。
|
|
|
|
|
|
|
|
mastodonとの違いはserverがui(html)を提供しないところです。これは、clientがui(html)を提供します。
|
|
|
|
|
|
|
|
やり取りは主にchat形式になります。roomが存在し、気に入ったroomに入り、投稿と閲覧を行います。
|
|
|
|
|
|
|
|
dmも可能です。この場合も通常はroomが作成されます。
|
|
|
|
|
|
|
|
### dendrite
|
|
|
|
|
|
|
|
前回、matrixのdendrite-serverを立てたのですが、今回はdendriteのupgradeを試みてわかったことを書きます。
|
|
|
|
|
|
|
|
dendriteは、goで書かれたmatrix-serverです。
|
|
|
|
|
|
|
|
monolith, polylithがあり、polylithは大規模用です。single-userはmonolithを選択しましょう。
|
|
|
|
|
|
|
|
まず、DBですが、postgresql(pgsql)を使用しましょう。sqliteは正常に動作しません。
|
|
|
|
|
|
|
|
https://matrix-org.github.io/dendrite/installation/database
|
|
|
|
|
|
|
|
latestもpgsqlならconfig(v2)で動作します。v1との違いは`database`と`jetstream`あたりです。
|
|
|
|
|
|
|
|
#### latest config
|
|
|
|
|
|
|
|
flyでdomainの`$app.fly.dev`をcertしておいてください。
|
|
|
|
|
|
|
|
```yml:dendrite.yaml
|
|
|
|
version: 2
|
|
|
|
|
|
|
|
global:
|
|
|
|
server_name: xxx.fly.dev
|
2024-04-23 13:28:33 +00:00
|
|
|
#well_known_server_name: "syui.ai:443"
|
2024-04-23 13:21:26 +00:00
|
|
|
private_key: /etc/dendrite/matrix_key.pem
|
|
|
|
key_validity_period: 168h0m0s
|
|
|
|
trusted_third_party_id_servers:
|
|
|
|
- matrix.org
|
|
|
|
- vector.im
|
|
|
|
|
|
|
|
database:
|
|
|
|
connection_string: postgres://db.internal:5432/?sslmode=disable
|
|
|
|
max_open_conns: 100
|
|
|
|
max_idle_conns: 5
|
|
|
|
conn_max_lifetime: -1
|
|
|
|
|
|
|
|
jetstream:
|
|
|
|
in_memory: false
|
|
|
|
storage_path: /data/
|
|
|
|
topic_prefix: Dendrite
|
|
|
|
|
|
|
|
metrics:
|
|
|
|
enabled: false
|
|
|
|
basic_auth:
|
|
|
|
username: metrics
|
|
|
|
password: metrics
|
|
|
|
|
|
|
|
dns_cache:
|
|
|
|
enabled: true
|
|
|
|
cache_size: 4000
|
|
|
|
cache_lifetime: 300
|
|
|
|
|
|
|
|
app_service_api:
|
|
|
|
database:
|
|
|
|
connection_string: postgres://db.internal:5432/?sslmode=disable
|
|
|
|
max_open_conns: 10
|
|
|
|
max_idle_conns: 2
|
|
|
|
conn_max_lifetime: -1
|
|
|
|
config_files: []
|
|
|
|
|
|
|
|
client_api:
|
|
|
|
registration_disabled: true
|
|
|
|
registration_shared_secret: ""
|
|
|
|
enable_registration_captcha: false
|
|
|
|
recaptcha_public_key: ""
|
|
|
|
recaptcha_private_key: ""
|
|
|
|
recaptcha_bypass_secret: ""
|
|
|
|
recaptcha_siteverify_api: ""
|
|
|
|
|
|
|
|
turn:
|
|
|
|
turn_user_lifetime: ""
|
|
|
|
turn_uris: []
|
|
|
|
turn_shared_secret: ""
|
|
|
|
turn_username: ""
|
|
|
|
turn_password: ""
|
|
|
|
|
|
|
|
rate_limiting:
|
|
|
|
enabled: true
|
|
|
|
threshold: 5
|
|
|
|
cooloff_ms: 500
|
|
|
|
|
|
|
|
edu_server:
|
|
|
|
|
|
|
|
federation_api:
|
|
|
|
federation_certificates: []
|
|
|
|
|
|
|
|
federation_sender:
|
|
|
|
database:
|
|
|
|
connection_string: postgres://db.internal:5432/?sslmode=disable
|
|
|
|
max_open_conns: 10
|
|
|
|
max_idle_conns: 2
|
|
|
|
conn_max_lifetime: -1
|
|
|
|
send_max_retries: 16
|
|
|
|
disable_tls_validation: false
|
|
|
|
|
|
|
|
proxy_outbound:
|
|
|
|
enabled: true
|
|
|
|
protocol: http
|
|
|
|
host: localhost
|
|
|
|
port: 8008
|
|
|
|
|
|
|
|
key_server:
|
|
|
|
database:
|
|
|
|
connection_string: postgres://db.internal:5432/?sslmode=disable
|
|
|
|
max_open_conns: 10
|
|
|
|
max_idle_conns: 2
|
|
|
|
conn_max_lifetime: -1
|
|
|
|
|
|
|
|
media_api:
|
|
|
|
database:
|
|
|
|
connection_string: postgres://db.internal:5432/?sslmode=disable
|
|
|
|
max_open_conns: 10
|
|
|
|
max_idle_conns: 2
|
|
|
|
conn_max_lifetime: -1
|
|
|
|
base_path: /data/media
|
|
|
|
max_file_size_bytes: 10485760
|
|
|
|
dynamic_thumbnails: false
|
|
|
|
max_thumbnail_generators: 10
|
|
|
|
thumbnail_sizes:
|
|
|
|
- width: 32
|
|
|
|
height: 32
|
|
|
|
method: crop
|
|
|
|
- width: 96
|
|
|
|
height: 96
|
|
|
|
method: crop
|
|
|
|
- width: 640
|
|
|
|
height: 480
|
|
|
|
method: scale
|
|
|
|
|
|
|
|
room_server:
|
|
|
|
database:
|
|
|
|
connection_string: postgres://db.internal:5432/?sslmode=disable
|
|
|
|
max_open_conns: 10
|
|
|
|
max_idle_conns: 2
|
|
|
|
conn_max_lifetime: -1
|
|
|
|
|
|
|
|
signing_key_server:
|
|
|
|
database:
|
|
|
|
connection_string: postgres://db.internal:5432/?sslmode=disable
|
|
|
|
max_open_conns: 10
|
|
|
|
max_idle_conns: 2
|
|
|
|
conn_max_lifetime: -1
|
|
|
|
|
|
|
|
key_perspectives:
|
|
|
|
- server_name: matrix.org
|
|
|
|
keys:
|
|
|
|
- key_id: ed25519:auto
|
|
|
|
public_key: Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw
|
|
|
|
- key_id: ed25519:a_RXGa
|
|
|
|
public_key: l8Hft5qXKn1vfHrg3p4+W8gELQVo8N13JkluMfmn2sQ
|
|
|
|
|
|
|
|
prefer_direct_fetch: false
|
|
|
|
|
|
|
|
sync_api:
|
|
|
|
database:
|
|
|
|
connection_string: postgres://db.internal:5432/?sslmode=disable
|
|
|
|
max_open_conns: 10
|
|
|
|
max_idle_conns: 2
|
|
|
|
conn_max_lifetime: -1
|
|
|
|
|
|
|
|
user_api:
|
|
|
|
account_database:
|
|
|
|
connection_string: postgres://db.internal:5432/?sslmode=disable
|
|
|
|
max_open_conns: 10
|
|
|
|
max_idle_conns: 2
|
|
|
|
conn_max_lifetime: -1
|
|
|
|
device_database:
|
|
|
|
connection_string: postgres://db.internal:5432/?sslmode=disable
|
|
|
|
max_open_conns: 10
|
|
|
|
max_idle_conns: 2
|
|
|
|
conn_max_lifetime: -1
|
|
|
|
|
|
|
|
tracing:
|
|
|
|
enabled: false
|
|
|
|
jaeger:
|
|
|
|
serviceName: ""
|
|
|
|
disabled: false
|
|
|
|
rpc_metrics: false
|
|
|
|
tags: []
|
|
|
|
sampler: null
|
|
|
|
reporter: null
|
|
|
|
headers: null
|
|
|
|
baggage_restrictions: null
|
|
|
|
throttler: null
|
|
|
|
|
|
|
|
logging:
|
|
|
|
- type: file
|
|
|
|
level: info
|
|
|
|
params:
|
|
|
|
path: /var/log/dendrite
|
|
|
|
```
|
|
|
|
|
|
|
|
#### old config
|
|
|
|
|
|
|
|
```yml:dendrite.yaml
|
|
|
|
version: 1
|
|
|
|
|
|
|
|
global:
|
|
|
|
server_name: <my-matrix.domain>
|
|
|
|
|
|
|
|
private_key: /etc/dendrite/matrix_key.pem
|
|
|
|
key_validity_period: 168h0m0s
|
|
|
|
trusted_third_party_id_servers:
|
|
|
|
- matrix.org
|
|
|
|
- vector.im
|
|
|
|
|
|
|
|
kafka:
|
|
|
|
addresses:
|
|
|
|
- kafka:9092
|
|
|
|
|
|
|
|
topic_prefix: Dendrite
|
|
|
|
|
|
|
|
use_naffka: true
|
|
|
|
|
|
|
|
naffka_database:
|
|
|
|
connection_string: file:///data/dendrite.db
|
|
|
|
max_open_conns: 10
|
|
|
|
max_idle_conns: 2
|
|
|
|
conn_max_lifetime: -1
|
|
|
|
|
|
|
|
metrics:
|
|
|
|
enabled: false
|
|
|
|
|
|
|
|
basic_auth:
|
|
|
|
username: metrics
|
|
|
|
password: metrics
|
|
|
|
|
|
|
|
dns_cache:
|
|
|
|
enabled: false
|
|
|
|
|
|
|
|
cache_size: 256
|
|
|
|
cache_lifetime: 300
|
|
|
|
|
|
|
|
app_service_api:
|
|
|
|
internal_api:
|
|
|
|
listen: http://0.0.0.0:7777
|
|
|
|
connect: http://appservice_api:7777
|
|
|
|
database:
|
|
|
|
connection_string: file:///data/dendrite.db
|
|
|
|
max_open_conns: 10
|
|
|
|
max_idle_conns: 2
|
|
|
|
conn_max_lifetime: -1
|
|
|
|
|
|
|
|
config_files: []
|
|
|
|
|
|
|
|
client_api:
|
|
|
|
internal_api:
|
|
|
|
listen: http://0.0.0.0:7771
|
|
|
|
connect: http://client_api:7771
|
|
|
|
external_api:
|
|
|
|
listen: http://0.0.0.0:8071
|
|
|
|
|
|
|
|
registration_disabled: false
|
|
|
|
|
|
|
|
registration_shared_secret: ""
|
|
|
|
|
|
|
|
enable_registration_captcha: false
|
|
|
|
|
|
|
|
recaptcha_public_key: ""
|
|
|
|
recaptcha_private_key: ""
|
|
|
|
recaptcha_bypass_secret: ""
|
|
|
|
recaptcha_siteverify_api: ""
|
|
|
|
|
|
|
|
turn:
|
|
|
|
turn_user_lifetime: ""
|
|
|
|
turn_uris: []
|
|
|
|
turn_shared_secret: ""
|
|
|
|
turn_username: ""
|
|
|
|
turn_password: ""
|
|
|
|
|
|
|
|
rate_limiting:
|
|
|
|
enabled: true
|
|
|
|
threshold: 5
|
|
|
|
cooloff_ms: 500
|
|
|
|
|
|
|
|
edu_server:
|
|
|
|
internal_api:
|
|
|
|
listen: http://0.0.0.0:7778
|
|
|
|
connect: http://edu_server:7778
|
|
|
|
|
|
|
|
federation_api:
|
|
|
|
internal_api:
|
|
|
|
listen: http://0.0.0.0:7772
|
|
|
|
connect: http://federation_api:7772
|
|
|
|
external_api:
|
|
|
|
listen: http://0.0.0.0:8072
|
|
|
|
|
|
|
|
federation_certificates: []
|
|
|
|
|
|
|
|
federation_sender:
|
|
|
|
internal_api:
|
|
|
|
listen: http://0.0.0.0:7775
|
|
|
|
connect: http://federation_sender:7775
|
|
|
|
database:
|
|
|
|
connection_string: file:///data/dendrite.db
|
|
|
|
max_open_conns: 10
|
|
|
|
max_idle_conns: 2
|
|
|
|
conn_max_lifetime: -1
|
|
|
|
|
|
|
|
send_max_retries: 16
|
|
|
|
|
|
|
|
disable_tls_validation: false
|
|
|
|
|
|
|
|
proxy_outbound:
|
|
|
|
enabled: false
|
|
|
|
protocol: http
|
|
|
|
host: localhost
|
|
|
|
port: 8080
|
|
|
|
|
|
|
|
key_server:
|
|
|
|
internal_api:
|
|
|
|
listen: http://0.0.0.0:7779
|
|
|
|
connect: http://key_server:7779
|
|
|
|
database:
|
|
|
|
connection_string: file:///data/dendrite.db
|
|
|
|
max_open_conns: 10
|
|
|
|
max_idle_conns: 2
|
|
|
|
conn_max_lifetime: -1
|
|
|
|
|
|
|
|
media_api:
|
|
|
|
internal_api:
|
|
|
|
listen: http://0.0.0.0:7774
|
|
|
|
connect: http://media_api:7774
|
|
|
|
external_api:
|
|
|
|
listen: http://0.0.0.0:8074
|
|
|
|
database:
|
|
|
|
connection_string: file:///data/dendrite.db
|
|
|
|
max_open_conns: 10
|
|
|
|
max_idle_conns: 2
|
|
|
|
conn_max_lifetime: -1
|
|
|
|
|
|
|
|
base_path: /data/media
|
|
|
|
|
|
|
|
max_file_size_bytes: 10485760
|
|
|
|
|
|
|
|
dynamic_thumbnails: false
|
|
|
|
|
|
|
|
max_thumbnail_generators: 10
|
|
|
|
|
|
|
|
thumbnail_sizes:
|
|
|
|
- width: 32
|
|
|
|
height: 32
|
|
|
|
method: crop
|
|
|
|
- width: 96
|
|
|
|
height: 96
|
|
|
|
method: crop
|
|
|
|
- width: 640
|
|
|
|
height: 480
|
|
|
|
method: scale
|
|
|
|
|
|
|
|
room_server:
|
|
|
|
internal_api:
|
|
|
|
listen: http://0.0.0.0:7770
|
|
|
|
connect: http://room_server:7770
|
|
|
|
database:
|
|
|
|
connection_string: file:///data/dendrite.db
|
|
|
|
max_open_conns: 10
|
|
|
|
max_idle_conns: 2
|
|
|
|
conn_max_lifetime: -1
|
|
|
|
|
|
|
|
signing_key_server:
|
|
|
|
internal_api:
|
|
|
|
listen: http://0.0.0.0:7780
|
|
|
|
connect: http://signing_key_server:7780
|
|
|
|
database:
|
|
|
|
connection_string: file:///data/dendrite.db
|
|
|
|
max_open_conns: 10
|
|
|
|
max_idle_conns: 2
|
|
|
|
conn_max_lifetime: -1
|
|
|
|
|
|
|
|
key_perspectives:
|
|
|
|
- server_name: matrix.org
|
|
|
|
keys:
|
|
|
|
- key_id: ed25519:auto
|
|
|
|
public_key: Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw
|
|
|
|
- key_id: ed25519:a_RXGa
|
|
|
|
public_key: l8Hft5qXKn1vfHrg3p4+W8gELQVo8N13JkluMfmn2sQ
|
|
|
|
|
|
|
|
prefer_direct_fetch: false
|
|
|
|
|
|
|
|
sync_api:
|
|
|
|
internal_api:
|
|
|
|
listen: http://0.0.0.0:7773
|
|
|
|
connect: http://sync_api:7773
|
|
|
|
external_api:
|
|
|
|
listen: http://0.0.0.0:8073
|
|
|
|
database:
|
|
|
|
connection_string: file:///data/dendrite.db
|
|
|
|
max_open_conns: 10
|
|
|
|
max_idle_conns: 2
|
|
|
|
conn_max_lifetime: -1
|
|
|
|
|
|
|
|
user_api:
|
|
|
|
internal_api:
|
|
|
|
listen: http://0.0.0.0:7781
|
|
|
|
connect: http://user_api:7781
|
|
|
|
account_database:
|
|
|
|
connection_string: file:///data/dendrite.db
|
|
|
|
max_open_conns: 10
|
|
|
|
max_idle_conns: 2
|
|
|
|
conn_max_lifetime: -1
|
|
|
|
device_database:
|
|
|
|
connection_string: file:///data/dendrite.db
|
|
|
|
max_open_conns: 10
|
|
|
|
max_idle_conns: 2
|
|
|
|
conn_max_lifetime: -1
|
|
|
|
|
|
|
|
tracing:
|
|
|
|
enabled: false
|
|
|
|
jaeger:
|
|
|
|
serviceName: ""
|
|
|
|
disabled: false
|
|
|
|
rpc_metrics: false
|
|
|
|
tags: []
|
|
|
|
sampler: null
|
|
|
|
reporter: null
|
|
|
|
headers: null
|
|
|
|
baggage_restrictions: null
|
|
|
|
throttler: null
|
|
|
|
|
|
|
|
logging:
|
|
|
|
- type: file
|
|
|
|
level: info
|
|
|
|
params:
|
|
|
|
path: /var/log/dendrite
|
|
|
|
```
|
|
|
|
|
|
|
|
ref : https://codeberg.org/gerald/dendrite-on-flyio/src/branch/main/dendrite-example.yaml
|
|
|
|
|
|
|
|
#### registration_shared_secret
|
|
|
|
|
|
|
|
`registration_disabled`がtrueの場合、`create-account`コマンドでaccoutが作成できません。`registration_shared_secret`が必要です。
|
|
|
|
|
|
|
|
config(v1)のv0.5.0までなかったようですが、config(v2)のv0.6.0から追加されました。spam対策のようですが、非常に厄介です。
|
|
|
|
|
|
|
|
```
|
|
|
|
client_api:
|
|
|
|
registration_disabled: false
|
|
|
|
guests_disabled: true
|
|
|
|
registration_shared_secret: ""
|
|
|
|
enable_registration_captcha: false
|
|
|
|
recaptcha_public_key: ""
|
|
|
|
recaptcha_private_key: ""
|
|
|
|
recaptcha_bypass_secret: ""
|
|
|
|
```
|
|
|
|
|
|
|
|
```sh
|
|
|
|
$ curl localhost:8008/_synapse/admin/v1/register
|
|
|
|
```
|
|
|
|
|
|
|
|
```sh
|
|
|
|
$ fly ssh console
|
|
|
|
$ /usr/bin/create-account -config /etc/dendrite/dendrite.yaml -username xxx -password xxx -admin
|
|
|
|
```
|
|
|
|
|
|
|
|
#### IMPORTANT: Process file descriptor limit is currently 1024
|
|
|
|
|
|
|
|
```sh:Dockerfile.txt
|
|
|
|
FROM matrixdotorg/dendrite-monolith
|
|
|
|
|
|
|
|
COPY ./entrypoint.sh /entrypoint.sh
|
|
|
|
ENTRYPOINT ["/entrypoint.sh"]
|
|
|
|
```
|
|
|
|
|
|
|
|
```sh:entrypoint.sh
|
|
|
|
#!/bin/sh
|
|
|
|
ulimit -n 65535
|
|
|
|
ulimit -u 4096
|
|
|
|
exec /usr/bin/dendrite-monolith-server --tls-cert /etc/dendrite/server.crt --tls-key /etc/dendrite/server.key --config /etc/dendrite/dendrite.yaml
|
|
|
|
```
|
|
|
|
|
|
|
|
https://matrix-org.github.io/dendrite/installation/start/optimisation
|
|
|
|
|
|
|
|
https://fly.io/laravel-bytes/integrating-the-elastic-stack-elk-into-a-laravel-app-on-fly/
|
|
|
|
|
|
|
|
#### fly cretificate
|
|
|
|
|
|
|
|
```sh
|
|
|
|
$ flyctl ips list
|
|
|
|
$ flyctl certs create example.com
|
|
|
|
$ flyctl certs show example.com
|
|
|
|
```
|
|
|
|
|
|
|
|
https://fly.io/docs/app-guides/custom-domains-with-fly/#teaching-your-app-about-custom-domains
|
|
|
|
|
|
|
|
#### domain
|
|
|
|
|
|
|
|
`https://example.com/.well-known/matrix/server`
|
|
|
|
|
|
|
|
```sh
|
|
|
|
{
|
|
|
|
"m.server": "matrix.example.com:8448"
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
`.well-known`は`$app.fly.dev`を使用したほうがいいかもしれません。
|
|
|
|
|
|
|
|
```yml:dendrite.yaml
|
|
|
|
global:
|
|
|
|
server_name: example.com
|
|
|
|
well_known_server_name: "example.com:443"
|
|
|
|
```
|
|
|
|
|
|
|
|
fly cret+cloudflareで連携したdomainが通らなかった。
|
|
|
|
|
|
|
|
#### panic
|
|
|
|
|
|
|
|
様々な問題を解決してもpanic(golang)で動かないことがあります。これは、fly.ioのcpu, memoryの問題っぽい気がしますが、planをupgradeすれば解決するかもしれません。
|
|
|
|
|
|
|
|
#### 遅い
|
|
|
|
|
|
|
|
遅い、とにかく遅い...。
|
|
|
|
|
2024-04-23 13:28:33 +00:00
|
|
|
matrix.orgとsyui.aiでやり取りしてみたのですが、非常に遅かったので、今後、高速化をやっていきたいのですが、fly.ioのplanを上げるしかないのか、あるいは、dendrite-configとかでなんとかなるものなのか、わかりません。
|
2024-04-23 13:21:26 +00:00
|
|
|
|
|
|
|
![](https://raw.githubusercontent.com/syui/img/master/other/matrix-server-origin_0003.png)
|
|
|
|
|
|
|
|
### 注釈
|
|
|
|
|
|
|
|
正確には、matrixはprotocolで、dendriteがserver、elementがclientです。
|
|
|
|
|
|
|
|
しかし、matrixをserverと表現することがあります。なぜなら、そのほうが伝わりやすいからです。
|
|
|
|
|
|
|
|
例えばここでdendrite-serverは...と書いてもなんのことかわかりません。
|
|
|
|
|
|
|
|
ですが、matrix-serverと書くと「ああ、あのchatのserverか」とわかりますよね。
|
|
|
|
|
|
|
|
これは、mastodonを紹介するときにもよくあることです。
|
|
|
|
|
|
|
|
例えば、mastodonを紹介するとき、mastodonをprotocolのように扱ってしまうことがあるのです。
|
|
|
|
|
|
|
|
mastodonはserver, clientであり、protocolではありません。
|
|
|
|
|
|
|
|
mastodonは昔、ostatusというprotocolで動いていましたが、すぐにactivitypubに移行して、それからずっとactivitypubです。
|
|
|
|
|
|
|
|
しかし、mastodonは広く認知されているので、protocolのように扱い、そのように表現してしまうことが多々あるような気がしています。
|
|
|
|
|
|
|
|
mastodonのように広がり、繋がるsnsという場合、厳密には、activitypub等のprotocolで繋がっているのであって、mastodon自体は単なるserverでありclientです。
|
|
|
|
|
|
|
|
protocolより遥かに狭いものなのですが、mastodonは有名なので、protocolのように広い概念のようなものとして表現し、扱ってしまうことがあります。あまり気にしていませんが。
|
|
|
|
|
|
|
|
とは言え、ここで書かれていることは、正確ではないため、注釈しておくことにします。
|
|
|
|
|