日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷解決方案
是時(shí)候閉環(huán)Java應(yīng)用了

你曾經(jīng)因?yàn)椴渴?上線而痛苦嗎?你曾經(jīng)因?yàn)橐ミ\(yùn)維那改配置而煩惱嗎?在我接觸過(guò)的一些部署/上線方式中,曾碰到過(guò)以下一些問(wèn)題:

創(chuàng)新互聯(lián)建站是一家專注于做網(wǎng)站、成都網(wǎng)站制作與策劃設(shè)計(jì),哈巴河網(wǎng)站建設(shè)哪家好?創(chuàng)新互聯(lián)建站做網(wǎng)站,專注于網(wǎng)站建設(shè)十多年,網(wǎng)設(shè)計(jì)領(lǐng)域的專業(yè)建站公司;建站業(yè)務(wù)涵蓋:哈巴河等地區(qū)。哈巴河做網(wǎng)站價(jià)格咨詢:13518219792

1、程序代碼和依賴都是人工上傳到服務(wù)器,不是通過(guò)工具進(jìn)行部署和發(fā)布;

2、目錄結(jié)構(gòu)沒(méi)有規(guī)范,jar啟動(dòng)時(shí)通過(guò)-classpath任意指定;

3、fat jar,把程序代碼、配置文件和依賴jar都打包到一個(gè)jar中,改配置文件太費(fèi)勁;

4、不管是非web應(yīng)用還是web應(yīng)用都部署到web容器環(huán)境,如Tomcat;

5、web應(yīng)用還需要先在服務(wù)器上安裝好環(huán)境(如裝Tomcat)才能部署,想升級(jí)版本或者換個(gè)容器太難了;

6、線上參數(shù)修改還需要找運(yùn)維,痛苦。

還有如沒(méi)有自動(dòng)部署平臺(tái),回滾到上一個(gè)版本那可真是天方夜談;增量包而非全量包,無(wú)法自由在在的回滾;前端代碼直接覆蓋而非版本化,難快速回滾,出問(wèn)題要清理CDN,痛苦;ngx_lua項(xiàng)目時(shí)不按照項(xiàng)目的方式部署,在服務(wù)器上隨意修改代碼,導(dǎo)致某些服務(wù)器忘記修改或者版本不一致,排查問(wèn)題太痛苦。

還有很多部署中不好的方式,但是本文只關(guān)注閉環(huán)Java應(yīng)用帶來(lái)的好處。首先介紹下應(yīng)該如何部署應(yīng)用,然后介紹下什么是閉環(huán)Java應(yīng)用,它的好處和如何搭建。

應(yīng)該如何部署應(yīng)用

項(xiàng)目

項(xiàng)目中應(yīng)該包括了所有要執(zhí)行的代碼、啟停腳本,比如非web應(yīng)用

web應(yīng)用

打包應(yīng)用后,會(huì)按照相應(yīng)的目錄結(jié)構(gòu)構(gòu)建。如果項(xiàng)目使用maven,可以使用maven-assembly-plugin進(jìn)行按照相應(yīng)的目錄結(jié)構(gòu)構(gòu)件。

即項(xiàng)目、打包的應(yīng)用要按照統(tǒng)一的風(fēng)格來(lái)實(shí)施。

自動(dòng)部署系統(tǒng)

自動(dòng)部署系統(tǒng)負(fù)責(zé)打包應(yīng)用(比如執(zhí)行mvn相應(yīng)的命令即可)、抽包(從指定目錄抽取要部署的代碼,如target/nonweb-example-package目錄)、部署代碼(發(fā)布代碼,將代碼同步到宿主機(jī)器)、啟停應(yīng)用(配置指定的啟停腳本并調(diào)用)。

自動(dòng)部署除了這些功能外,應(yīng)該還有如發(fā)布?xì)v史管理(回滾)、分組管理(如不同機(jī)房不同的配置文件)、配置管理(如要修改啟動(dòng)/停止腳本、修改配置文件[不同機(jī)房不同的配置]、參數(shù)管理[如jvm參數(shù)等])等。

宿主機(jī)器

即代碼部署到的機(jī)器,它應(yīng)該只安裝最小化環(huán)境,如只需要裝JDK即可,像Tomcat是不需要安裝的,由應(yīng)用決定使用哪個(gè)容器。

通過(guò)增加自動(dòng)部署系統(tǒng)可以更好的進(jìn)行項(xiàng)目的統(tǒng)一發(fā)布、管理和回滾。

閉環(huán)Java應(yīng)用

閉環(huán)Java應(yīng)用指Java代碼、容器、配置文件、啟停腳本等都在同一處維護(hù),修改配置文件、修改環(huán)境參數(shù)、更改容器類型等都不需要到宿主機(jī)器上進(jìn)行更改。宿主機(jī)器只提供基本運(yùn)行環(huán)境,如僅部署JDK環(huán)境即可,不需要部署如Tomcat容器,需要什么容器,都是在Java應(yīng)用中指定。

這樣的好處是配置文件修改、JVM參數(shù)修改、容器的選擇都可以在Java應(yīng)用中配置,形成閉環(huán)。

閉環(huán)Java應(yīng)用的目的主要是讓Java應(yīng)用能自啟動(dòng),這樣程序的控制權(quán)就在我們手里,而不是運(yùn)維手里。而我們更懂我們的程序。

隨著微服務(wù)概念的流行,spring boot也受到大家的熱捧。spring boot能幫助我們快速構(gòu)建基于spring的應(yīng)用;其能方便創(chuàng)建自啟動(dòng)應(yīng)用、可以嵌入各種容器(如Tomcat、Jetty)、提供了一些starter pom用于簡(jiǎn)化配置文件、自動(dòng)化配置(只需要引入相關(guān)的pom,就自動(dòng)獲得了某些功能)等。

在介紹spring boot之前,我們看下在以前是怎么構(gòu)建閉環(huán)Java應(yīng)用。

從零構(gòu)建非web應(yīng)用

項(xiàng)目結(jié)構(gòu)

本示例演示了構(gòu)建一個(gè)非web應(yīng)用RPC服務(wù)生產(chǎn)者(如Dubbo服務(wù)),還可以構(gòu)建如Worker類型的應(yīng)用,他們本身不需要web容器,作為普通的java應(yīng)用啟動(dòng)即可。

maven依賴(pom.xml)

需要自己添加如spring-core、spring-context等相關(guān)依賴,此處就不展示了。

打包配置(pom.xml)

nonweb-example\pom.xml

 
 
  1.     org.apache.maven.plugins
  2.     maven-assembly-plugin
  3.     2.6
  4.     
  5.         src/assembly/assembly.xml
  6.         ${project.build.finalName}
  7.     
  8.     
  9.         
  10.             package
  11.             
  12.                 directory
  13.             
  14.         
  15.     

使用maven-assembly-plugin進(jìn)行打包;打包配置如下:

 
 
  1. package
  2.     dir
  3. false
  4.     
  5.     
  6.         src/bin
  7.         bin
  8.         
  9.             *.bat
  10.         
  11.         dos
  12.     
  13.     
  14.         src/bin
  15.         bin
  16.         
  17.             *.sh
  18.         
  19.         unix
  20.         0755
  21.     
  22.     
  23.     
  24.         ${project.build.directory}/classes
  25.         classes
  26.     
  27.     
  28.         lib
  29.         
  30.             com.jd:nonweb-example
  31.         
  32.     

主要有三組配置:

  • formats:打包格式,此處使用的是dir,還可以是zip、rar等;
  • fileSet:拷貝文件,本示例主要有bin文件、classes文件需要拷貝;
  • dependencySets:依賴jar,拷貝到lib目錄;

執(zhí)行mvn package后形成了將得到如下結(jié)構(gòu):

將該目錄通過(guò)自動(dòng)部署抽包并部署到宿主機(jī)器即可。然后自動(dòng)部署系統(tǒng)執(zhí)行bin下的啟停腳本執(zhí)行即可。

啟動(dòng)類

 
 
  1. public class Bootstrap {
  2.   public static void main(String[] args) throws Exception {
  3.       ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:spring-config.xml");
  4.       ctx.registerShutdownHook();
  5.       Thread.currentThread().join();
  6.   }
  7. }

本示例沒(méi)有使用Java Config方式構(gòu)建,直接加載spring配置文件啟動(dòng)Java應(yīng)用。

啟動(dòng)腳本

 
 
  1. #!/bin/sh
  2. echo -------------------------------------------
  3. echo start server
  4. echo -------------------------------------------
  5. # 設(shè)置項(xiàng)目代碼路徑
  6. export CODE_HOME="/export/App/nonweb-example-startup-package"
  7. #日志路徑
  8. export LOG_PATH="/export/Logs/nonweb.example.jd.local"
  9. mkdir -p $LOG_PATH
  10. # 設(shè)置依賴路徑
  11. export CLASSPATH="$CODE_HOME/classes:$CODE_HOME/lib/*"
  12. # java可執(zhí)行文件位置
  13. export _EXECJAVA="$JAVA_HOME/bin/java"
  14. # JVM啟動(dòng)參數(shù)
  15. export JAVA_OPTS="-server -Xms128m -Xmx256m -Xss256k -XX:MaxDirectMemorySize=128m"
  16. # 啟動(dòng)類
  17. export MAIN_CLASS=com.jd.nonweb.example.startup.Bootstrap
  18. $_EXECJAVA $JAVA_OPTS -classpath $CLASSPATH $MAIN_CLASS &
  19. tail -f $LOG_PATH/stdout.log

配置項(xiàng)目代碼路徑、日志路徑、依賴路徑、java執(zhí)行文件路徑、JVM啟動(dòng)參數(shù)、啟動(dòng)類。

停止腳本

 
 
  1. #日志路徑
  2. export LOG_PATH="/export/Logs/nonweb.example.jd.local"
  3. mkdir -p $LOG_PATH
  4. # 啟動(dòng)類
  5. export MAIN_CLASS=com.jd.nonweb.example.startup.Bootstrap
  6. echo -------------------------------------------
  7. echo stop server
  8. #所有相關(guān)進(jìn)程
  9. PIDs=`jps -l | grep $MAIN_CLASS | awk '{print $1}'`
  10. #停止進(jìn)程
  11. if [ -n "$PIDs" ]; then
  12.   for PID in $PIDs; do
  13.       kill $PID
  14.       echo "kill $PID"
  15.   done
  16. fi
  17. #等待50秒
  18. for i in 1 10; do
  19.   PIDs=`jps -l | grep $MAIN_CLASS | awk '{print $1}'`
  20.   if [ ! -n "$PIDs" ]; then
  21.     echo "stop server success"
  22.     echo -------------------------------------------
  23.     break
  24.   fi
  25.   echo "sleep 5s"
  26.   sleep 5
  27. done
  28. #如果等待50秒還沒(méi)有停止完,直接殺掉
  29. PIDs=`jps -l | grep $MAIN_CLASS | awk '{print $1}'`
  30. if [ -n "$PIDs" ]; then
  31.   for PID in $PIDs; do
  32.       kill -9 $PID
  33.       echo "kill -9 $PID"
  34.   done
  35. fi
  36. tail -fn200 $LOG_PATH/stdout.log

到此一個(gè)閉環(huán)非web應(yīng)用就構(gòu)建完了,啟停腳本、啟動(dòng)類、項(xiàng)目代碼都是統(tǒng)一在一處維護(hù),并使用maven-assembly-plugin將這些打包在一起,通過(guò)自動(dòng)部署發(fā)布并執(zhí)行,達(dá)到了閉環(huán)的目的。

從零構(gòu)建web應(yīng)用

項(xiàng)目結(jié)構(gòu)

maven依賴(pom.xml)

需要自己添加如spring-core、spring-context、spring-web、spring-webmvc、velocity等相關(guān)依賴,此處就不展示了。

打包配置(pom.xml)

web-example\pom.xml

 
 
  1.     org.apache.maven.plugins
  2.     maven-assembly-plugin
  3.     2.6
  4.     
  5.         src/assembly/assembly.xml
  6.         ${project.build.finalName}
  7.     
  8.     
  9.         
  10.             package
  11.             
  12.                 directory
  13.             
  14.         
  15.     

使用maven-assembly-plugin進(jìn)行打包;打包配置如下:

 
 
  1. package
  2.     dir
  3. false
  4.     
  5.         src/bin
  6.         bin
  7.         
  8.             *.sh
  9.         
  10.         unix
  11.         0755
  12.     
  13.     
  14.     
  15.         src/main/webapp
  16.         
  17.     
  18.     
  19.     
  20.         ${project.build.directory}/classes
  21.         WEB-INF/classes
  22.     
  23.     
  24.         WEB-INF/lib
  25.         
  26.             com.jd:web-example
  27.         
  28.     

主要有三組配置:

  • formats:打包格式,此處使用的是dir,還可以是zip、rar等;
  • fileSet:拷貝文件,本示例主要有bin文件、classes文件、webapp文件需要拷貝;
  • dependencySets:依賴jar,拷貝到WEB-INF\lib目錄;

執(zhí)行mvn package后形成了將得到如下結(jié)構(gòu):

打包的目錄結(jié)構(gòu)和普通web結(jié)構(gòu)完全一樣;將該目錄通過(guò)自動(dòng)部署抽包并發(fā)布到宿主機(jī)器即可。然后自動(dòng)部署系統(tǒng)執(zhí)行bin下的啟停腳本執(zhí)行即可。

啟動(dòng)類

 
 
  1. public class TomcatBootstrap {
  2.   private static final Logger LOG = LoggerFactory.getLogger(TomcatBootstrap.class);
  3.   public static void main(String[] args) throws Exception{
  4.     //提升性能(https://wiki.apache.org/tomcat/HowTo/FasterStartUp)
  5.     System.setProperty("tomcat.util.scan.StandardJarScanFilter.jarsToSkip", "*.jar");
  6.     //System.setProperty("securerandom.source","file:/dev/./urandom");
  7.     int port =Integer.parseInt(System.getProperty("server.port", "8080"));
  8.     String contextPath = System.getProperty("server.contextPath", "");
  9.     String docBase = System.getProperty("server.docBase", getDefaultDocBase());
  10.     LOG.info("server port : {}, context path : {},doc base : {}",port, contextPath, docBase);
  11.     Tomcat tomcat = createTomcat(port,contextPath, docBase);
  12.     tomcat.start();
  13.      Runtime.getRuntime().addShutdownHook(new Thread() {
  14.         @Override
  15.         public void run(){
  16.             try {
  17.                 tomcat.stop();
  18.             } catch (LifecycleException e) {
  19.                 LOG.error("stoptomcat error.", e);
  20.             }
  21.         }
  22.     });
  23.     tomcat.getServer().await();
  24.   }
  25.   private static String getDefaultDocBase() {
  26.    File classpathDir = new File(Thread.currentThread().getContextClassLoader().getResource(".").getFile());
  27.    File projectDir =classpathDir.getParentFile().getParentFile();
  28.    return new File(projectDir,"src/main/webapp").getPath();
  29.   }
  30.  private static Tomcat createTomcat(int port,String contextPath, String docBase) throws Exception{
  31.     String tmpdir = System.getProperty("java.io.tmpdir");
  32.     Tomcat tomcat = new Tomcat();
  33.     tomcat.setBaseDir(tmpdir);
  34.     tomcat.getHost().setAppBase(tmpdir);
  35.     tomcat.getHost().setAutoDeploy(false);
  36.     tomcat.getHost().setDeployOnStartup(false);
  37.     tomcat.getEngine().setBackgroundProcessorDelay(-1);
  38.     tomcat.setConnector(newNioConnector());
  39.     tomcat.getConnector().setPort(port);
  40.     tomcat.getService().addConnector(tomcat.getConnector()); 
  41.     Context context =tomcat.addWebapp(contextPath, docBase);
  42.     StandardServer server =(StandardServer) tomcat.getServer();
  43.     //APR library loader. Documentation at /docs/apr.html
  44.     server.addLifecycleListener(new AprLifecycleListener());
  45.     //Prevent memory leaks due to use of particularjava/javax APIs
  46.     server.addLifecycleListener(new JreMemoryLeakPreventionListener());
  47.     return tomcat;
  48.   }
  49.   //在這里調(diào)整參數(shù)優(yōu)化
  50.   private static Connector newNioConnector() {
  51.     Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
  52.     Http11NioProtocol protocol =(Http11NioProtocol) connector.getProtocolHandler();
  53.     return connector;
  54.   }
  55. }

通過(guò)嵌入Tomcat容器啟動(dòng),這種方式的確定是需要先寫Tomcat的啟動(dòng)代碼,優(yōu)點(diǎn)也很明顯:以后Tomcat的控制權(quán)在我們手中,可以隨時(shí)進(jìn)行切換或者優(yōu)化,不需要改線上的配置文件。

啟動(dòng)腳本

 
 
  1. #!/bin/sh
  2. echo -------------------------------------------
  3. echo start server
  4. echo -------------------------------------------
  5. # 設(shè)置項(xiàng)目代碼路徑
  6. export CODE_HOME="/export/App/web-example-web-package"
  7. #日志路徑
  8. export LOG_PATH="/export/Logs/web.example.jd.local"
  9. mkdir -p $LOG_PATH
  10. # 設(shè)置依賴路徑
  11. export CLASSPATH="$CODE_HOME/WEB-INF/classes:$CODE_HOME/WEB-INF/lib/*"
  12. # java可執(zhí)行文件位置
  13. export _EXECJAVA="$JAVA_HOME/bin/java"
  14. # JVM啟動(dòng)參數(shù)
  15. export JAVA_OPTS="-server -Xms128m -Xmx256m -Xss256k-XX:MaxDirectMemorySize=128m"
  16. # 服務(wù)端端口、上下文、項(xiàng)目根配置
  17. export SERVER_INFO="-Dserver.port=8090 -Dserver.contextPath=-Dserver.docBase=$CODE_HOME"
  18. # 啟動(dòng)類
  19. export MAIN_CLASS=com.jd.web.example.startup.TomcatBootstrap
  20. $_EXECJAVA $JAVA_OPTS -classpath $CLASSPATH $SERVER_INFO $MAIN_CLASS &
  21. tail -f $LOG_PATH/stdout.log

配置項(xiàng)目代碼路徑、日志路徑、依賴路徑、java執(zhí)行文件路徑、JVM啟動(dòng)參數(shù)、啟動(dòng)類;相當(dāng)于非web應(yīng)用,多了web服務(wù)器端口、上下文、項(xiàng)目根路徑配置。

停止腳本

和非web的類似就不再重復(fù)了。

到此一個(gè)閉環(huán)web應(yīng)用就構(gòu)建完了,啟停腳本、啟動(dòng)類、項(xiàng)目代碼都是統(tǒng)一在一處維護(hù),并使用maven-assembly-plugin將這些打包在一起,通過(guò)自動(dòng)部署發(fā)布并執(zhí)行。達(dá)到了閉環(huán)的目的。

Spring Boot構(gòu)建非web/web應(yīng)用

項(xiàng)目結(jié)構(gòu)

maven依賴(pom.xml)

spring-boot-example/pom.xml繼承spring-boot-starter-parent

 
 
  1.     org.springframework.boot
  2.     spring-boot-starter-parent
  3.     1.4.1.BUILD-SNAPSHOT

spring-boot-starter-parent中是一些通用配置,如JDK編碼、依賴管理(它又繼承了spring-boot-dependencies,這里邊定義了所有依賴);

依賴

 
 
  1.     org.springframework.boot
  2.     spring-boot-starter
  3.     org.springframework.boot
  4.     spring-boot-starter-web
  5.     org.springframework.boot
  6.     spring-boot-starter-velocity
  7.     org.springframework.boot
  8.     spring-boot-starter-log4j2

spring-boot-starter是最小化的spring boot環(huán)境(spring-core、spring-context等);spring-boot-starter-web是spring mvc環(huán)境,并使用Tomcat作為web容器;spring-boot-starter-velocity將自動(dòng)將模板引擎配置為velocity。此處可以看到starter的好處了,需要什么功能只需要引入一個(gè)starter,相關(guān)的依賴自動(dòng)添加,而且會(huì)自動(dòng)配置使用該特性。

打包配置(pom.xml)

spring-boot-example-web\pom.xml添加如下maven插件:

 
 
  1.     org.springframework.boot
  2.     spring-boot-maven-plugin

執(zhí)行mvn package時(shí)將得到如下fat jar:

啟動(dòng)類

 
 
  1. package com.jd.springboot.example.web.startup;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. import org.springframework.context.annotation.ImportResource;
  5. @SpringBootApplication(scanBasePackages = "com.jd.springboot.example")
  6. @ImportResource("classpath:spring-config.xml")
  7. public class Bootstrap {
  8.   public static void main(String[] args) {
  9.       SpringApplication.run(Bootstrap.class, args);
  10.   }
  11. }

@SpringBootApplication指定了要掃描的包、可以使用@ImportResource引入xml配置文件。然后可以直接作為普通java應(yīng)用啟動(dòng)即可,此時(shí)自動(dòng)使用tomcat作為web容器啟動(dòng)。

運(yùn)行 jar -jar spring-boot-example-1.0-SNAPSHOT.jar即可啟動(dòng)(META-INF\MANIFEST.MF指定了Main-Class)。

個(gè)人不太喜歡fat jar的方式??梢允褂胢aven-assembly-plugin配合來(lái)打包Java應(yīng)用。項(xiàng)目結(jié)構(gòu)如下所示:

項(xiàng)目結(jié)構(gòu)和之前的區(qū)別是多了assembly和bin。

打包配置(pom.xml)

spring-boot-example-web\pom.xml將如下maven插件

 
 
  1.     org.springframework.boot
  2.     spring-boot-maven-plugin

更改為assembly插件

 
 
  1.     org.apache.maven.plugins
  2.     maven-assembly-plugin
  3.     2.6
  4.     
  5.         src/assembly/assembly.xml
  6.         ${project.build.finalName}
  7.     
  8.     
  9.         
  10.             package
  11.             
  12.                 directory
  13.             
  14.         
  15.     

assembly.xml和“從零構(gòu)建非web應(yīng)用”的類似,就不貼配置了。

執(zhí)行mvn package時(shí)將得到如下打包:

啟停腳本也是類似的,在此也不貼配置了。到此基于spring boot的非fat jar方式的自啟動(dòng)Java應(yīng)用就構(gòu)建好了。

總結(jié)

從零構(gòu)建非web應(yīng)用/web應(yīng)用需要我們查找相關(guān)依賴并配置,還需要進(jìn)行一些配置(Spring配置、容器配置),如果構(gòu)建一個(gè)新的項(xiàng)目還是相對(duì)較慢的,但是在公司內(nèi)大家應(yīng)該都有自己的“starter pom”,因此實(shí)際構(gòu)建也不會(huì)很慢。而如果沒(méi)有一些項(xiàng)目的積累,使用spring boot可以非常容易而且快速的就能搭建出想要的項(xiàng)目。使用spring boot后:容易添加依賴、啟動(dòng)類不用自己創(chuàng)建、享受到自動(dòng)配置的好處等;而自帶的spring-boot-maven-plugin會(huì)生成fat jar,不過(guò)可以配合maven-assembly-plugin來(lái)實(shí)現(xiàn)之前的方式的。

另外因筆者所在公司使用Docker容器,一個(gè)宿主機(jī)器只部署一個(gè)JVM示例,示例中的啟停腳本不用考慮單機(jī)多JVM實(shí)例問(wèn)題。

創(chuàng)建閉環(huán)Java應(yīng)用,可以更容易的進(jìn)行如JVM參數(shù)調(diào)優(yōu)、修改容器配置文件、非web應(yīng)用不需要部署到Tomcat容器中;這是筆者想進(jìn)行閉環(huán)Java應(yīng)用的主要目的。

【本文是專欄作者張開(kāi)濤的原創(chuàng)文章,作者微信公眾號(hào):開(kāi)濤的博客( kaitao-1234567)】


標(biāo)題名稱:是時(shí)候閉環(huán)Java應(yīng)用了
分享鏈接:http://www.dlmjj.cn/article/djoehoh.html