访问页面升级访问_容器化单页面应用中RESTful API的访问

访问页面升级访问_容器化单页面应用中RESTful API的访问最近在工作中,需要让运行在容器中的单页面应用程序能够访问外部的RESTfulAPI。这个需求看起来并不困难,不过实现起来还是有些曲折的。在此,我就将这部分内容总结一下。在入正题之前,有个一问题,就是为什么要将单页面应用放在容器中运行?这个问题其实跟“为什么要将应用程序容器化”是一个问题。简单来讲,容器化的应用程序可以运行在任何具有容器执行环境的宿主平台上,比如可以在Linux系统中运行…

大家好,又见面了,我是你们的朋友全栈君。

最近在工作中,需要让运行在容器中的单页面应用程序能够访问外部的RESTful API。这个需求看起来并不困难,不过实现起来还是有些曲折的。在此,我就将这部分内容总结一下。

6e3949f9a57776b40a0cf56b96fec12e.png

在入正题之前,有个一问题,就是为什么要将单页面应用放在容器中运行?这个问题其实跟“为什么要将应用程序容器化”是一个问题。简单来讲,容器化的应用程序可以运行在任何具有容器执行环境的宿主平台上,比如可以在Linux系统中运行容器,也可以在MacOS或者Windows下使用Docker Desktop for Mac或者Docker for Windows来运行容器化的应用程序。无论在什么平台中运行,容器化的应用程序都可以使用统一化的配置方式(比如环境变量、虚拟磁盘路径的挂载等),并向外界提供一致的访问端点。将应用程序容器化最重要的一点是,通过它可以非常方便地将应用程序部署在云环境中,使应用程序具有很好的横向扩展能力,而且跨云的迁移也变得非常便捷。由此可见,通过容器的使用,我们可以采用不同的技术来实现应用程序的不同部分,然后可以得到统一的部署和运维体验,这一点对于微服务架构的实践有着非常深远的意义。在工作中,我所接触的系统包含了多个团队的贡献,有的团队使用nodejs,有的团队使用Scala,有的团队使用Go,这些独立分散的项目都以一个个独立的服务进行开发和交付,最终通过容器化的途径实现了整个应用程序的一体化部署。当然,与各种软件架构风格类似,微服务架构也是有利有弊,并不是所有的项目和团队都应该采用这种架构,还是应该根据项目和团队的实际情况来决定软件系统的架构方式,这部分内容就不在此过多讨论了。

回到本文的主题,我会通过一个案例来总结在不同场景下,容器化单页面应用访问RESTful API的方式。

我们的案例是一个提供名称列表的RESTful API,外加一个显示名称列表的前端单页面应用。不必理会什么是“名称列表”,它只不过是一个字符串列表。在这里我们也不必关心这个字符串列表包含哪些内容,只要让单页面应用能够访问到这个RESTful API即可。继续阅读本文,你将了解到这个案例是多么的简单。

RESTful API

首先创建一个能够返回名称列表的RESTful API,实现方式有很多种,我选择我熟悉的ASP.NET Core Web API项目来创建RESTful API。在命令行执行以下命令以创建一个ASP.NET Core Web API的项目:

dotnet new webapi --name NameList.Service

然后,使用Visual Studio Code编辑器打开该项目,删除ValuesController,然后新增NamesController,当然也可以基于ValuesController修改,代码如下:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Threading.Tasks;

using Microsoft.AspNetCore.Mvc;

namespace NameList.Service.Controllers

{

    [Route("api/[controller]")]

    [ApiController]

    public class NamesController : ControllerBase

    {

        [HttpGet]

        public ActionResultstring>> Get()

            => new string[] { "Brian", "Frank", "Sunny", "Chris" };

    }

}

目前我们不需要启用HTTPS重定向,将其从Startup.cs中删除,同时调整launchSettings.json文件,直接侦听http://*:5000,然后使用dotnet run命令,启动RESTful API,使用cURL工具进行测试:

1

2

$ curl -s http://localhost:5000/api/names

["Brian","Frank","Sunny","Chris"]

API调用成功。为了后续的实验能够顺利进行,我们在服务端启用CORS:

public class Startup

{

    private const string CorsPolicy = "DefaultCorsPolicy";

    public void ConfigureServices(IServiceCollection services)

    {

        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

        services.AddCors(options =>

        {

            options.AddPolicy(CorsPolicy, builder =>

            {

                builder.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin();

            });

        });

    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)

    {

        app.UseCors(CorsPolicy);

    }

}

接下来,开发我们的前端单页面应用,以调用该API并将名称列表显示在前端页面。

单页面应用

同样,前端页面也可以采用很多种框架和技术进行开发,比如使用React、Vue或者Angular,或者直接使用jQuery,都可以完成我们的目标。我还是选择我最熟悉的Angular 7,依照下面的步骤开发这个单页面应用。

首先,使用Angular CLI,创建我们的应用程序:

在回答几个问题之后(使用默认选项即可),前端单页面应用也就创建好了,首先在app.modules.ts中启用HttpClientModule:

import { BrowserModule } from '@angular/platform-browser';

import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';

import { HttpClientModule } from '@angular/common/http';

@NgModule({

  declarations: [

    AppComponent

  ],

  imports: [

    BrowserModule,

    HttpClientModule

  ],

  providers: [],

  bootstrap: [AppComponent]

})

export class AppModule { }

然后,在environment.ts和environment.prod.ts中加入RESTful API的BaseURI:

接着,新建一个AppService服务(app.service.ts),在该服务中提供一个getNames的方法,用以调用RESTful API以获取名称列表,并将获得的列表返回给调用方:

import { Injectable } from '@angular/core';

import { HttpClient } from '@angular/common/http';

import { Observable, of } from 'rxjs';

import { catchError, tap } from 'rxjs/operators';

import { environment } from 'src/environments/environment';

@Injectable({

  providedIn: 'root'

})

export class AppService {

  constructor(private http: HttpClient) { }

  getNames(): Observable {

    return this.http.get(`${environment.serviceUri}/api/names`)

    .pipe(

      tap(_ => console.log('fetched names')),

      catchError(this.handleError([]))

    );

  }

  private handleError(result?: T) {

    return (error: any): Observable => {

      console.error(error);

      return of(result as T);

    };

  }

}

然后,修改app.component.ts,以便在页面初始化的时候,调用AppService获取名称列表,并将获得的列表保存在变量中:

import { Component, OnInit } from '@angular/core';

import { AppService } from './app.service';

@Component({

  selector: 'app-root',

  templateUrl: './app.component.html',

  styleUrls: ['./app.component.css']

})

export class AppComponent implements OnInit {

  names: string[];

  constructor(private appService: AppService) { }

  ngOnInit(): void {

    this.getNames();

  }

  getNames(): void {

    this.appService.getNames()

      .subscribe(names => this.names = names);

  }

}

最后,修改app.component.html,通过HTML将获得的名称列表显示在页面上:

<h2>Namesh2>

<ul>

  <li *ngFor="let name of names">

    <span>{
{name}}
span>

  li>

ul>

现在,将RESTful API运行起来,然后使用ng serve命令将前端页面也运行起来,应该能够看到下面的效果:

ff3988b6630d0826b3bdb761b39f6725.png

接下来,我们将RESTful API和前端页面编译成容器镜像(Docker Images)。

现在,我们将上面开发的单页面应用编译成docker镜像,然后让它在容器中运行。在Angular项目的根目录下,新建一个Dockerfile,内容如下:

FROM nginx AS base

WORKDIR /app

EXPOSE 80

FROM node:10.16.0-alpine AS build

RUN npm install -g @angular/cli@8.0.3

WORKDIR /src

COPY . .

RUN npm install

RUN ng build --prod --output-path /app

FROM base AS final

COPY --from=build /app /usr/share/nginx/html

CMD ["nginx", "-g", "daemon off;"]

大概介绍一下,在上面的Dockerfile中,将nginx定义为base image,因为最终我会将Angular单页面应用运行在nginx上;然后,基于node镜像,安装Angular CLI,并将本地前端代码复制到容器中的/src目录下进行编译,最终将编译输出的html、js、css以及相关资源复制到nginx容器的/usr/share/nginx/html目录下,最后启动nginx来服务单页面应用站点。

现在,我们启动RESTful API,依旧让其侦听5000端口,然后通过以下docker命令,启动这个前端单页面应用容器:

1

docker run -it -p 8088:80 namelist-client

容器启动后,打开浏览器,访问8088端口,我们可以得到同样的结果,可以注意到,前端页面会发送请求到http://localhost:5000以获得名称列表:

783c6925e104c591db99a1094d41041f.png

整个实验看似已经非常成功,但是,我们忽略了一个重要问题,目前RESTful API的地址在前端代码中是写死(hard code)的,即使是在environment.prod.ts文件中指定,也是编译时就已经确定的事情,那如果RESTful API部署在不同的机器上,或者侦听端口不是5000呢?这样的话,前端单页面应用是无法访问RESTful API服务的。下面我们就来解决这个问题。

有一种比较简单粗暴的办法,就是在编译的时候,通过持续集成环境的设置,将RESTful API的地址写入environment.prod.ts文件中,但这样编译出来的容器只能在特定环境下运行,否则前端页面还是无法访问RESTful API。要让容器能够通用,还是应该在容器启动的时候,以环境变量的方式将RESTful API的地址注入到容器中。在此,我们讨论两种场景:RESTful API独立部署的场景,以及RESTful API也以容器的方式运行的场景。

RESTful API独立部署的场景

首先做个实验,将前端Angular项目中environment.prod.ts里的serviceUri改为一个相对路径,比如:

1

2

3

4

export const environment = {

  production: true,

  serviceUri: '/name-service'

};

重新将前端应用编译成docker镜像并执行,不出意料,页面无法正确加载,因为调用的RESTful API地址不正确,调用返回404:

4c695778637d53dfab39cfeeb87d1d64.png

接下来,可以使用nginx的反向代理功能,将/name-service的部分proxy_pass到真实的RESTful API地址,而真实的RESTful API地址可以在nginx的配置中通过读取环境变量来动态设置。在前端代码的根目录下,新建nginx.conf文件:

load_module "modules/ngx_http_perl_module.so";

env API_URI;

events {

    worker_connections 1024;

}

http {

    perl_set $api_uri 'sub { return $ENV{"API_URI"}; }';

    server {

      listen        80;

      server_name   localhost;

      include  /etc/nginx/mime.types;

      location / {

        root /usr/share/nginx/html;

        index  index.html  index.htm;

      }

      location ~ ^/name-service/(.*)$ {

        rewrite ^ $request_uri;

        rewrite ^/name-service/(.*)$ $1 break;

        return 400;

      }

    }

}

该配置文件通过使用nginx的perl模块,读取系统环境变量并在nginx中使用这个环境变量,然后设置location,指定当客户端请求/name-service时,将请求proxy_pass到由API_URI环境变量设置的RESTful API地址。由于需要使用perl模块,所以,Dockerfile也要做相应修改:

FROM nginx:perl AS base

WORKDIR /app

EXPOSE 80

FROM node:10.16.0-alpine AS build

RUN npm install -g @angular/cli@8.0.3

WORKDIR /src

COPY . .

RUN npm install

RUN ng build --prod --output-path /app

FROM base AS final

COPY --from=build /app /usr/share/nginx/html

COPY --from=build /src/nginx.conf /etc/nginx/nginx.conf

CMD ["nginx", "-g", "daemon off;"]

Base Image由nginx改为nginx:perl,然后需要将nginx.conf文件复制到nginx容器中的/etc/nginx目录。之后,重新编译前端docker镜像。

现在,启动容器时就可以使用-e参数指定RESTful API的地址了:

1

docker run -it -p 8088:80 -e API_URI=192.168.0.107:5000 namelist-client

再次刷新前端页面,可以看到,页面正确显示,API调用成功:

b08dc6bd7873314a611901b516e5e66d.png

RESTful API容器化的场景

如果我们将RESTful API也容器化,并与前端应用一起在容器中运行,那么就可以使用容器连接的方式,让前端页面访问后端的API。此时,只需要对前端nginx.conf进行一些修改:

events {

    worker_connections 1024;

}

http {

    server {

      listen        80;

      server_name   localhost;

      include  /etc/nginx/mime.types;

      location / {

        root /usr/share/nginx/html;

        index  index.html  index.htm;

      }

      location ~ ^/name-service/(.*)$ {

      }

    }

    upstream namelist-service {

        server namelist-service:5000;

    }

}

分别使用以下两条命令启动RESTful API和前端应用容器:

1

2

docker run -it --name namelist-service namelist-service

docker run -it -p 8088:80 --link namelist-service namelist-client

注意到在启动前端应用容器时,需要使用—link参数链接到namelist-service容器,而且服务端也不需要暴露出TCP端口,起到了一定的保护作用:

8439649f1922ae84dbca8176360f6997.png

本文以容器为背景,结合nginx的使用,介绍了容器化单页面应用中访问RESTful API的两种方法。由于单页面应用无法读取系统的环境变量,因此,解决RESTful API访问地址的问题就变得稍微有点复杂。本文相关的案例源代码:https://github.com/daxnet/name-list。

原文地址:https://sunnycoding.cn/2019/06/22/accessing-restful-api-in-dockerized-spa/


.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com 

49ab21fac8259456b7793179970884f7.png

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/149844.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • gamma校正什么意思_串联滞后校正对系统性能的影响

    gamma校正什么意思_串联滞后校正对系统性能的影响【Gamma的由来】首先,要区分照度和亮度,照度是一个客观的量,亮度是一个主观的量,不同的人看相同照度的物体所感受到的亮度是不一样的。对于照度线性变化的物体,人眼感受到的亮度不是线性的。人眼对于低照度的物体更敏感,这意味着对于照度为2、3、4的三个物体,人眼能够区分,而对于照度为222、223、224的三个物体,人眼不能区分。其次,我们存储颜色的空间是有限的,常用的RGBA32格式,每个颜色通道只有8位,最多能表示256种照度,而现实世界中的照度远超256。基于人眼对照度的感知特点,我们不能线性的去

    2022年9月22日
    0
  • 管理学第三章_企业集团管理第五章自测

    管理学第三章_企业集团管理第五章自测文章目录主要内容项目范围6个过程范围管理的重要性总表5.1范围管理概述5.2规划范围管理5.3收集需求主要内容项目范围6个过程(1)规划范围管理:对如何定义、确认和控制项目范围的过程进行描述。(2)收集需求:为实现项目目标,明确并记录项目干系人的相关需求的过程。(3)定义范围:详细描述产品范围和项目范围,编制项目范围说明书,作为以后项目决策的基础。(4)刨建工作分解结构(WBS):把整个项目工作分解为较小的、易于管理的组成部分,形成一个自上而下的分解结构。(5)确认范围:正式验收已完成的可交付

    2022年9月22日
    0
  • 通过Zimbra收取POP3邮件,总是提示错误:Connection reset

    通过Zimbra收取POP3邮件,总是提示错误:Connection reset

    2021年5月11日
    114
  • ant 编译java(java是干啥的)

    1.什么是antant是构建工具2.什么是构建概念到处可查到,形象来说,你要把代码从某个地方拿来,编译,再拷贝到某个地方去等等操作,当然不仅与此,但是主要用来干这个3.ant的好处跨平台–因为ant是使用java实现的,所以它跨平台使用简单–与ant的兄弟make比起来语法清晰–同样是和make相比功能强大–ant能做的事情很多,可能你用了很久,你仍然不知道它能有多少功能。当你自己开发…

    2022年4月11日
    47
  • rac 10g 10.2.0.1升级到10.2.0.5具体解释[通俗易懂]

    rac 10g 10.2.0.1升级到10.2.0.5具体解释

    2022年2月6日
    53
  • Jmeter—正则表达式提取器:模板&匹配数字详解「建议收藏」

    Jmeter—正则表达式提取器:模板&匹配数字详解「建议收藏」目录一、相关理论1.正则表达式2.模板3.匹配数字二、例子1.【模板&匹配数字】2.【例-贪婪&非贪婪】3.【例-普通】一、相关理论1.正则表达式():要提取的内容.:匹配任意单个字符串*:匹配(之前的符号)0次或多次+:匹配(+之前的符号)1次或多次?:不要太贪婪,在找到第一个匹配项后停止。.:匹配连续0个/多个字符.+:匹配连续1个/多个字符\:转义,.表示匹配字符.本身2.模板表示取哪几个括号中的值若模板为:000,则为整个表达式匹配到的内容(这里为整个响

    2022年9月10日
    0

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号