体験!マイコンボードで組込みLinux

第8回組込みWebサーバの構築

Webサーバ構築について

SH7706LSRボードに載せたLinux上でも、PC Linuxと同様にWebサーバを構築することができます。PC LinuxでWebサーバを構築したりそれを利用する場合の代表的な用途としては、ネットワークによる情報発信があります。SH7706LSRボードも機能的にはPCと同等なので、Webサーバを構築してネットワークによる情報発信を行うこともできますが、組込みボードそのものは一般的にはネットワークによる情報発信のために購入することはあまりないと思います。組込みボードは携帯機器の制御や計測制御などが主な用途なので、Webサーバの用途としては、携帯機器の機器設定や計測制御用のコントロールパネルが考えられます。

前準備

SH7706LSRボードなどの組込みボードでWebサーバを構築する前に、Webサーバ構築をしたことがない場合はあらかじめPC Linux上でWebサーバ構築の練習をしておくといいでしょう。PC LinuxでのWebサーバ構築は定番のひとつなので、市販書籍をはじめとする情報量が豊富なので情報に事欠くことはないと思います。

PC LinuxでのWebサーバ構築ではHTML文書の表示とともに、CGIプログラムの構築も行います。また、SH7706LSRボードなどの組込みボードでWebサーバで計測制御用途に使う場合は、計測や制御のプログラム開発もできるようにしておかなければなりません。

一般的にはLinux上で計測や制御を行う場合はデバイスドライバを使うのが定番となっていますが、Linuxデバイスドライバの作成は情報量が少なくハードルも高い傾向にあるので、SH7706LSRではアプリケーションプログラムからでも直接にハードウェアを操作できるサンプルが公開されています。そのようなサンプルを使えば、Linux上のアプリケーションプログラムでも比較的簡単に計測や制御のためのハードウェア操作が直接行えます。ただ、割り込みなどはデバイスドライバで行うしかないようです。

CGIプログラム

HTMLのフォーム

単純にコンテンツを表示させるCGIプログラムならば、前回に紹介したとおりのプログラムで十分です。CGIプログラムは通常、ユーザの操作を受けてその内容に応じてHTML文書のコンテンツを動的に出力をします。CGIプログラムはHTML文書のコンテンツの出力をするという機能が核になってはいますが、ユーザの操作を受ける処理も重要になってきます。

HTML文書のタグにはCGIプログラムの動作を前提にしたフォームというタグの文法が定義されています。HTMLのフォームでは、チェックボタンやラジオボタンなどのボタンによるユーザ入力を受けつける機能や、テキストボックスによるユーザが入力したテキスト内容を受け付ける機能があります。

通常のプログラミング言語でのウィジェットではそのプログラミング言語だけで処理が閉じていてそれだけで完結していますが、HTMLのチェックボタンやラジオボタン、テキストボックスなどのウィジェットの操作によるアクションはHTML文書単体では対応していません。HTML文書自体は単なる紙芝居なので、それだけでは動的なウィジェットの操作に対応はできません。HTML文書のフォームタグはCGIプログラムとの連動が前提となっています。

フォームとCGI

CGIプログラムは標準出力でHTML文書のコンテンツを出力するだけなので、専用のプログラミング言語や特殊なツールキットなどは不要です。標準出力の機能さえあればいいので、どのような言語でも、シェルスクリプトでもかまいません。

ただCGIプログラムは、HTMLのフォームでのチェックボタンやラジオボタンなどのボタンによるユーザ入力やテキストボックスによるユーザが入力したテキスト内容を受け付ける必要がありますので、それらの入力内容を取得できるようになっていなければなりません。

WebサーバにおいてHTMLのフォームの入力内容は環境変数を介して受け取ります。したがって、CGIプログラムは標準出力機能だけではなく、環境変数の内容を取得できなければいけません。C言語においてはgetenv関数で環境変数の内容を取得でき、他の言語においてもたいていgetenv関数に相当する機能は揃っています。

Webサーバにおける環境変数

Webサーバでサポートする環境変数はいろいろとありますが、その中でもBusyBoxのhttpdでもサポートしている環境変数の概略を紹介します。Webサーバでの環境変数は実際に動作させ、表示させてみれば具体的に理解できると思います。

① HTTP_REFERER

CGIプログラムを呼び出したリンク元のURLを取得することができます。CGIプログラムから元のページに戻る場合のリンクを設定する場合に用いると便利です。ただし、CGIプログラムをブラウザで直接URL入力した場合とブックマークから呼び出した場合にNULLになります。

② HTTP_USER_AGENT

ブラウザのバージョン番号とプラットフォーム名を含んだクライアントの情報を取得することができます。HTMLの標準的な機能以外の拡張機能はブラウザの種類によってサポート内容が異なったりするので、クライアントにおけるブラウザの情報の内容に応じてCGIプログラムは臨機応変に対応することが可能になります。

③ REQUEST_METHOD

CGIプログラムへの要求方法を取得することができます。

④ QUERY_STRING

HTMLのフォームでのチェックボタンやラジオボタンなどのボタンによるユーザ入力やテキストボックスによるユーザが入力したテキスト内容を取得することができ、CGIプログラムの動作に必須な環境変数となっています。

⑤ SCRIPT_FILENAME

CGIプログラムが置かれているマシンのフルパスでのファイル名を取得することができます。

⑥ SCRIPT_NAME

CGIプログラムが置かれているマシンでのhttpdのドキュメントルートからの相対パス名を取得することができます。

⑦ SERVER_SOFTWARE

httpdに関するソフトウェア情報を取得することができます。

CGIプログラムの実際

環境変数の表示

CGIプログラムの有機的に動作させる場合はCGIプログラムで環境変数の内容を取得することが必須になってきますので、まずは環境変数の内容を表示させるCGIプログラムを作成し実際に動作をさせてみます。

環境変数の内容を表示させるCGIプログラムのソースはリスト1となります。リスト1は、基本的には前回紹介したCGIプログラムをベースに9~15行目にCGIに関連する環境変数を取得し出力する処理をしています。

リスト1 環境変数の内容を表示させるCGIプログラム
 1:    #include <stdio.h>
 2:    #include <string.h>
 3:    
 4:    int main() {
 5:        printf("Content-type: text/html\n\n");
 6:        printf("<html><body bgcolor=peachpuff>\n");
 7:        printf("<div align=center><big>LEDOUT Control Page</big></div>\n");
 8:        printf("<hr size=2 width=100%>\n<p>\n");
 9:        printf("HTTP_REFER = %s<br>\n", getenv("HTTP_REFERER"));
10:        printf("HTTP_USER_AGENT = %s<br>\n", getenv("HTTP_USER_AGENT"));
11:        printf("QUERY_STRING = %s<br>\n", getenv("QUERY_STRING"));
12:        printf("REQUEST_METHOD = %s<br>\n", getenv("REQUEST_METHOD"));
13:        printf("SCRIPT_FILENAME = %s<br>\n", getenv("SCRIPT_FILENAME"));
14:        printf("SCRIPT_NAME = %s<br>\n", getenv("SCRIPT_NAME"));
15:        printf("SERVER_SOFTWARE = %s<br>\n", getenv("SERVER_SOFTWARE"));
16:        printf("</body></html>\n");
17:    
18:        return 0;
19:    }

リスト1のCGIプログラムを呼び出すフォームを含むHTML文書はリスト2となります。

リスト2 リスト1のCGIを呼び出すフォーム
 1:    <html><body bgcolor=peachpuff>
 2:    <div align=center><big>LEDOUT Control Page</big></div>
 3:    <hr size=2 width=100%>
 4:    <p>The on board LED on SH7706LSR.</p>
 5:    <form method="put" action="cgi-bin/ledout.cgi">
 6:     <p>
 7:      LEDOUT : 
 8:      <input type="checkbox" name="ledout" value="on" /> ON <br>
 9:      <input type="submit" value="SUBMIT" />
10:      <input type="reset" value="RESET" />
11:     </p>
12:    </form>
13:    </body></html>

リスト2のフォームタグは5~12行目の部分となります。CGIプログラムを呼び出す部分は5行目で、呼び出し先のCGIプログラムのパスはcgi-bin/ledout.cgiとなっています。チェックボタンの定義は8行目でチェックをした場合のみCGIプログラムへのパラメータとして有効となり、名称はledout、値はonとなります。サブミットボタンは9行目、リセットボタンは10行目となります。

httpdのフォルダパスを/var/httpdとすると、リスト2のHTML文書は/var/httpd/ledout.html、リスト1のCGIプログラムは/var/httpd/cgi-bin/ledout.cgiとして配置します。IP設定を有効にしてから以下のようにhttpdを起動します。

# httpd -h /var/httpd

クライアントのブラウザからリスト2のHTML文書を表示させると、図1のようになります。チェックをせずにSUBMITを押すと図2のような表示になり、チェックをしてSUBMITを押すと図3のような表示になります。

図1 CGI呼び出しフォームの表示
図1 CGI呼び出しフォームの表示
図2 チェックボックスにチェックせずSUBMITした場合
図2 チェックボックスにチェックせずSUBMITした場合
図3 チェックボックスにチェックした場合
図3 チェックボックスにチェックした場合

フォーム情報の取得

今回はフォームのボタンの内容によりLEDの点滅を制御するCGIプログラムを作成しますが、その前にフォームの情報から個別ウィジェットの情報を取得できなければいけません。

フォームタグでは複数のウィジェットを記述することができ、それらの複数のウィジェットの多種多様な情報が1つの環境変数であるQUERY_STRINGに集約されていますので、その環境変数の中から目的とする個別ウィジェットの情報を取り出さなければなりません。それだけでなく、QUERY_STRING環境変数の内容は呼び出し側によるURLに付属して送信されるため、URLで扱える文字でなければならないので記号文字は文字コードになっています。

URLにおいてはスペースが許されないので、QUERY_STRING環境変数の内容は、スペース文字は「+」に変換されています。また個別ウィジェット間の区切りは「&」で、同じウィジェット間の名称と値の区切りは「=」となっています。QUERY_STRING環境変数から個別ウィジェットの情報を取り出して出力するのみのCGIプログラムがリスト3となります。

リスト3 QUERY_STRING環境変数から個別ウィジェットの情報を取り出して出力
 1:    #include <stdio.h>
 2:    #include <string.h>
 3:    
 4:    int get_param(char *str) {
 5:        char    *src, *dst;
 6:        int    i, code;
 7:    
 8:        src = dst = str;
 9:        while(*src != 0) {
10:            if(*src == '+') {
11:                *dst++ = ' ';
12:            } else if(*src == '%') {
13:                code = 0;
14:                for(i = 0;i < 2;i++) {
15:                    code <<= 4;
16:                    if(isdigit(src[i + 1])) code += src[i + 1] - '0';
17:                    else if(isupper(src[i + 1])) code += src[i + 1] - 'A' + 10;
18:                    else if(isdigit(src[i + 1])) code += src[i + 1] - 'a' + 10;
19:                }
20:                *dst++ = code;
21:                src += 2;
22:            } else {
23:                *dst++ = *src;
24:            }
25:            src++;
26:        }
27:        *dst = 0;
28:    }
29:    
30:    int main() {
31:        char    *param, *str;
32:        int    size;
33:    
34:        printf("Content-type: text/html\n\n");
35:        printf("<html><body bgcolor=peachpuff>\n");
36:        printf("<div align=center><big>LEDOUT Control Page</big></div>\n");
37:        printf("<hr size=2 width=100%>\n");
38:        printf("<p>The on board LED on SH7706LSR.</p>\n");
39:    
40:        str = getenv("QUERY_STRING");
41:        size = strlen(str);
42:        param = malloc(size + 1);
43:        memcpy(param, str, size + 1);
44:        str = strtok(param, "&=");
45:        while(str != NULL) {
46:            get_param(str);
47:            printf("<p>%s ", str);
48:            str = strtok(NULL,"&=");
49:            get_param(str);
50:            printf("%s</p>\n", str);
51:            str = strtok(NULL,"&=");
52:        }
53:    
54:        printf("<p><a href=%s>BACK</a></p>\n",getenv("HTTP_REFERER"));
55:        printf("</body></html>\n");
56:    
57:        free(param);
58:        return 0;
59:    }

注意としては、環境変数の情報は単なる参照のみは別としてユーザ領域のメモリではないので、基本的にはユーザ領域にコピーして使わなければなりません。リスト3ではQUERY_STRING環境変数の内容はユーザ領域にコピーして扱っています。

リスト3で、一括された環境変数の内容を個別ウィジェットの名称と変数まで分割して取り出す処理をしているのが44~52行目となります。また、get_param関数ではスペース文字を意味する「+」をスペースに戻したり、16進数の文字コードで表現されているものを元の記号文字に戻す処理をしています。

リスト3のCGIプログラムでは、チェックをせずにSUBMITを押すと図4のような表示になり、チェックをしてにSUBMITを押すと図5のような表示になります。

図4 チェックボックスにチェックせずSUBMITした場合
図4 チェックボックスにチェックせずSUBMITした場合
図5 チェックボックスにチェックした場合
図5 チェックボックスにチェックした場合

CGIプログラムによるI/O制御

リスト3のCGIプログラムをベースにして、実際にSH7706LSR内蔵LEDを点滅制御をするCGIプログラムがリスト4になります。

リスト4 SH7706LSR内蔵LEDの点滅制御CGI
 1:    #include <stdio.h>
 2:    #include <string.h>
 3:    #include <sys/types.h>
 4:    #include <sys/stat.h>
 5:    #include <fcntl.h>
 6:    #include <unistd.h>
 7:    #include <stdlib.h>
 8:    #include <stdio.h>
 9:    #include <string.h>
10:    #include <sys/mman.h>
11:    #include <asm/page.h>
12:    
13:    #define SCPDR 0x136
14:    #define LED 0x10
15:    
16:    int get_param(char *str) {
17:        char    *src, *dst;
18:        int    i, code;
19:    
20:        src = dst = str;
21:        while(*src != 0) {
22:            if(*src == '+') {
23:                *dst++ = ' ';
24:            } else if(*src == '%') {
25:                code = 0;
26:                for(i = 0;i < 2;i++) {
27:                    code <<= 4;
28:                    if(isdigit(src[i + 1])) code += src[i + 1] - '0';
29:                    else if(isupper(src[i + 1])) code += src[i + 1] - 'A' + 10;
30:                    else if(isdigit(src[i + 1])) code += src[i + 1] - 'a' + 10;
31:                }
32:                *dst++ = code;
33:                src += 2;
34:            } else {
35:                *dst++ = *src;
36:            }
37:            src++;
38:        }
39:        *dst = 0;
40:    }
41:    
42:    int main() {
43:        volatile unsigned char    *mmaped, scpdr;
44:        char    *param, *str;
45:        int    fd, i, size;
46:    
47:        printf("Content-type: text/html\n\n");
48:        printf("<html><body bgcolor=peachpuff>\n");
49:        printf("<div align=center><big>LEDOUT Control Page</big></div>\n");
50:        printf("<hr size=2 width=100%>\n");
51:        printf("<p>The on board LED on SH7706LSR.</p>\n");
52:        printf("<p><a href=%s>BACK</a></p>\n",getenv("HTTP_REFERER"));
53:        printf("</body></html>\n");
54:    
55:        fd = open("/dev/mem",O_RDWR);
56:        if(fd < 0) {
57:            fprintf(stderr,"cannot open /dev/mem\n");
58:            return -1;
59:        }
60:        mmaped = (volatile unsigned char*)mmap(0, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0xa4000000);
61:        close(fd);
62:        if(mmaped == MAP_FAILED) {
63:            fprintf(stderr,"cannot mmap\n");
64:            return -1;
65:        }
66:        str = getenv("QUERY_STRING");
67:        size = strlen(str);
68:        param = malloc(size + 1);
69:        memcpy(param, str, size + 1);
70:        str = strtok(param, "&=");
71:    
72:        mmaped[SCPDR] &= ~LED;
73:    
74:        while(str != NULL) {
75:            get_param(str);
76:            if(strcmp(str, "ledout") == 0) {
77:                mmaped[SCPDR] |= LED;
78:            }
79:            str = strtok(NULL,"&=");
80:            get_param(str);
81:            str = strtok(NULL,"&=");
82:        }
83:        munmap((char*)mmaped, PAGE_SIZE);
84:    
85:        free(param);
86:    
87:        return 0;
88:    }

リスト4のSH7706周辺制御レジスタを操作するための初期化処理は55~65行目となります。72行目では条件にかかわりなく一旦LEDを消灯させています。チェックボタンはチェックをした場合のみCGIプログラムへのパラメータとして有効となりますので、76~78行目では ledout という名称が検出できればLEDを点灯させています。

リスト4のCGIプログラムはリスト1のHTML文書にあるフォームにより呼び出されるようになっていますが、CGIプログラム自身にフォームタグを持たせるようにすればCGIプログラム単体のみでユーザ入力受付と制御を行うことができます。それがリスト5となります。また、チェックボックスではなくラジオボタンで制御をするように変更したCGIプログラムがリスト6となります。

リスト5 フォームまで含んだCGI
 1:    #include <stdio.h>
 2:    #include <string.h>
 3:    #include <sys/types.h>
 4:    #include <sys/stat.h>
 5:    #include <fcntl.h>
 6:    #include <unistd.h>
 7:    #include <stdlib.h>
 8:    #include <stdio.h>
 9:    #include <string.h>
10:    #include <sys/mman.h>
11:    #include <asm/page.h>
12:    
13:    #define SCPDR 0x136
14:    #define LED 0x10
15:    
16:    int get_param(char *str) {
17:        char    *src, *dst;
18:        int    i, code;
19:    
20:        src = dst = str;
21:        while(*src != 0) {
22:            if(*src == '+') {
23:                *dst++ = ' ';
24:            } else if(*src == '%') {
25:                code = 0;
26:                for(i = 0;i < 2;i++) {
27:                    code <<= 4;
28:                    if(isdigit(src[i + 1])) code += src[i + 1] - '0';
29:                    else if(isupper(src[i + 1])) code += src[i + 1] - 'A' + 10;
30:                    else if(isdigit(src[i + 1])) code += src[i + 1] - 'a' + 10;
31:                }
32:                *dst++ = code;
33:                src += 2;
34:            } else {
35:                *dst++ = *src;
36:            }
37:            src++;
38:        }
39:        *dst = 0;
40:    }
41:    
42:    int main() {
43:        volatile unsigned char    *mmaped, scpdr;
44:        char    *param, *str;
45:        int    fd, i, size, flag;
46:    
47:        fd = open("/dev/mem",O_RDWR);
48:        if(fd < 0) {
49:            fprintf(stderr,"cannot open /dev/mem\n");
50:            return -1;
51:        }
52:    
53:        mmaped = (volatile unsigned char*)mmap(0, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0xa4000000);
54:        close(fd);
55:        if(mmaped == MAP_FAILED) {
56:            fprintf(stderr,"cannot mmap\n");
57:            return -1;
58:        }
59:        str = getenv("QUERY_STRING");
60:        size = strlen(str);
61:        param = malloc(size + 1);
62:        memcpy(param, str, size + 1);
63:        str = strtok(param, "&=");
64:        mmaped[SCPDR] &= ~LED;
65:        flag = 0;
66:        while(str != NULL) {
67:            get_param(str);
68:            if(strcmp(str, "ledout") == 0) {
69:                mmaped[SCPDR] |= LED;
70:                flag = 1;
71:            }
72:            str = strtok(NULL,"&=");
73:            get_param(str);
74:            str = strtok(NULL,"&=");
75:        }
76:        munmap((char*)mmaped, PAGE_SIZE);
77:        free(param);
78:    
79:        printf("Content-type: text/html\n\n");
80:        printf("<html><body bgcolor=peachpuff>\n");
81:        printf("<div align=center><big>LEDOUT Control Page</big></div>\n");
82:        printf("<hr size=2 width=100%>\n");
83:        printf("<p>The on board LED on SH7706LSR.</p>\n");
84:        printf("<form method=put action=ledout.cgi>\n<p>\n");
85:        printf("LEDOUT:<input type=checkbox name=ledout value=on ");
86:        if(flag == 1) printf("checked");
87:        printf(" /> ON <br>\n");
88:        printf("<input type=submit value=SUBMIT />\n");
89:        printf("<input type=reset value=RESET />\n</p>\n</form>\n");
90:        printf("</body></html>\n");
91:    
92:        return 0;
93:    }
リスト6 フォームの入力方法を変更
  1:    #include <stdio.h>
  2:    #include <string.h>
  3:    #include <sys/types.h>
  4:    #include <sys/stat.h>
  5:    #include <fcntl.h>
  6:    #include <unistd.h>
  7:    #include <stdlib.h>
  8:    #include <stdio.h>
  9:    #include <string.h>
 10:    #include <sys/mman.h>
 11:    #include <asm/page.h>
 12:    
 13:    #define SCPDR 0x136
 14:    #define LED 0x10
 15:    
 16:    int get_param(char *str) {
 17:        char    *src, *dst;
 18:        int    i, code;
 19:    
 20:        src = dst = str;
 21:        while(*src != 0) {
 22:            if(*src == '+') {
 23:                *dst++ = ' ';
 24:            } else if(*src == '%') {
 25:                code = 0;
 26:                for(i = 0;i < 2;i++) {
 27:                    code <<= 4;
 28:                    if(isdigit(src[i + 1])) code += src[i + 1] - '0';
 29:                    else if(isupper(src[i + 1])) code += src[i + 1] - 'A' + 10;
 30:                    else if(isdigit(src[i + 1])) code += src[i + 1] - 'a' + 10;
 31:                }
 32:                *dst++ = code;
 33:                src += 2;
 34:            } else {
 35:                *dst++ = *src;
 36:            }
 37:            src++;
 38:        }
 39:        *dst = 0;
 40:    }
 41:    
 42:    int main() {
 43:        volatile unsigned char    *mmaped, scpdr;
 44:        char    *param, *str;
 45:        int    fd, i, size, flag, ledparam;
 46:    
 47:        fd = open("/dev/mem",O_RDWR);
 48:        if(fd < 0) {
 49:            fprintf(stderr,"cannot open /dev/mem\n");
 50:            return -1;
 51:        }
 52:    
 53:        mmaped = (volatile unsigned char*)mmap(0, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0xa4000000);
 54:        close(fd);
 55:        if(mmaped == MAP_FAILED) {
 56:            fprintf(stderr,"cannot mmap\n");
 57:            return -1;
 58:        }
 59:        str = getenv("QUERY_STRING");
 60:        size = strlen(str);
 61:        param = malloc(size + 1);
 62:        memcpy(param, str, size + 1);
 63:        str = strtok(param, "&=");
 64:        while(str != NULL) {
 65:            get_param(str);
 66:            if(strcmp(str, "ledout") == 0) {
 67:                ledparam = 1;
 68:            } else {
 69:                ledparam = 0;
 70:            }
 71:            str = strtok(NULL,"&=");
 72:            get_param(str);
 73:            if(ledparam == 1) {
 74:                if(strcmp(str, "on") == 0) {
 75:                    mmaped[SCPDR] |= LED;
 76:                    flag = 1;
 77:                }
 78:                if(strcmp(str, "off") == 0) {
 79:                    mmaped[SCPDR] &= ~LED;
 80:                    flag = 0;
 81:                }
 82:            }
 83:            str = strtok(NULL,"&=");
 84:        }
 85:        munmap((char*)mmaped, PAGE_SIZE);
 86:        free(param);
 87:    
 88:        printf("Content-type: text/html\n\n");
 89:        printf("<html><body bgcolor=peachpuff>\n");
 90:        printf("<div align=center><big>LEDOUT Control Page</big></div>\n");
 91:        printf("<hr size=2 width=100%>\n");
 92:        printf("<p>The on board LED on SH7706LSR.</p>\n");
 93:        printf("<form method=put action=ledout.cgi>\n<p>\n");
 94:        printf("LEDOUT : <input type=radio name=ledout value=on ");
 95:        if(flag == 1) printf("checked");
 96:        printf(" /> ON ");
 97:        printf("<input type=radio name=ledout value=off ");
 98:        if(flag == 0) printf("checked");
 99:        printf(" /> OFF <br>\n");
100:        printf("<input type=submit value=SUBMIT />\n");
101:        printf("<input type=reset value=RESET />\n</p>\n</form>\n");
102:        printf("</body></html>\n");
103:    
104:        return 0;
105:    }
図6 リスト6のCGIの実行
図6 リスト6のCGIの実行

次回は

今回まではコンパイラ言語であるC言語でのプログラミングを扱ってきましたが、CGIプログラムなどでもインタープリタ系言語も使えれば便利なので、SH7706LSRボード上のLinuxへのインタープリタ系言語の導入について解説します。

おすすめ記事

記事・ニュース一覧