wget 非遞歸下載

wget 非遞歸下載篇

1整體概括

上篇分析的是wget參數解析,本篇主要是分析wget非遞歸下載html或者文件。wget實際上就是通過sock 向web服務器發送http數據包(GET or POST),web服務器收到請求後,發回回覆包給wget,當然了http 傳輸層是tcp協議,簡單來說wget 發tcp包,發送內容符合http協議,web服務器解析(such as nginx、apache)請求包,針對請求回覆回覆包。so easy.

整個wget可以用下面的流程圖概括


其中config_analysis已經在前一篇已經分析過了,本篇就是分析wget後面的實現。

我們僅僅是不加任何參數的非遞歸下載,也就是執行如下命令:

wget www.baidu.com/index.html

2 代碼詳細解析

之前分析的是參數解析篇,參數解析完之後,會對參數進行校驗。此段代碼略過

nurl=argc –optind //參數解析完後,獲取用戶下載url個數,因爲執行的命令是

wget www.baidu.com/index.html所以 nurl==1

//code1:
url = alloca_array (char *, nurl + 1);
for (i = 0; i < nurl; i++, optind++)
{
char *rewritten = rewrite_shorthand_url (argv[optind]);//爲url增加http://
if (rewritten)
url[i] = rewritten;
else
url[i] = xstrdup (argv[optind]);
}

url[i] = NULL;//設置url最後元素爲空,作爲標記變量

上面那塊代碼主要是爲url分配內存,分配nurl+1枚個元素的char*數組。

函數rewrite_shorthand_url (argv[optind]) 主要是爲url添加http://字段,支持用戶不輸入協議,wget支持http、https、ftp協議,如果用戶沒輸入協議,默認http://。並且url最後一個元素置爲NULL,作爲標誌位。

//code2部分代碼:
for (t = url; *t; t++)
{
char *filename = NULL, *redirected_URL = NULL;
int dt, url_err;
/* Need to do a new struct iri every time, because
* retrieve_url may modify it in some circumstances,
* currently. */
struct iri *iri = iri_new ();
struct url *url_parsed;

set_uri_encoding (iri, opt.locale, true);
/*對url進行解析*/
url_parsed = url_parse (*t, &url_err, iri, true);
 
if (!url_parsed)
{
char *error = url_error (*t, url_err);
logprintf (LOG_NOTQUIET, "%s: %s.\n",*t, error);
xfree (error);
inform_exit_status (URLERROR);
}
else
{
	/*如果是遞歸or需要頁面css js之類的,並且不是ftp協議*/
if ((opt.recursive || opt.page_requisites)
&& (url_scheme (*t) != SCHEME_FTP || url_uses_proxy (url_parsed)))
/*此case爲遞歸url下載*/
{
int old_follow_ftp = opt.follow_ftp;
/* Turn opt.follow_ftp on in case of recursive FTP retrieval */
if (url_scheme (*t) == SCHEME_FTP)
opt.follow_ftp = 1;

retrieve_tree (url_parsed, NULL);
opt.follow_ftp = old_follow_ftp;                                                                                                                    
}
else
{
	/*此處爲非遞歸url下載*/
retrieve_url (url_parsed, *t, &filename, &redirected_URL, NULL,
&dt, opt.recursive, iri, true);
}



代碼遍歷url,對用戶的每一個url都進行下載。

函數set_uri_encoding主要是對url進行解析一個正常的url是如下格式:

scheme://host[:port][/path][;params][?query][#fragment],此函數就是對url解析出來每一個結構。如下:


url :					http://www.baidu.com/index.html
scheme:				SCHEME_HTTP
host:				www.baidu.com 
port:				80
path:				index.html
params:				NULL
query:				NULL
fragment:				NULL
file:					index.html
user:				NULL
passwd:				NULL

同時會對url進行utf-8編碼。

會根據用戶參數來決定是遞歸下載or非遞歸下載

遞歸下載條件:(用戶輸入-r or –p) && (not ftp協議 or use_proxy)

因爲我們是直接下載,所以會跳到

retrieve_url (url_parsed, *t,&filename, &redirected_URL, NULL,

&dt, opt.recursive, iri, true);

2.1 retrieve_url

//如果使用proxy會設置一些屬性,因爲沒有用proxy所以跳過了。

2.2 httploop

result = http_loop (u, orig_parsed,&mynewloc, &local_file, refurl, dt, proxy_url, iri);

參數說明:

u和orig_parsed是屬性是相同值

mynewloc 指向NULL。

local_file 指向NULL。

refurl指向NULL。

dt 爲 -1。

proxy_url 指向NULL。

iri爲上層分析的那個iri,包括編碼方式。

//code
hstat.referer = referer;//設置referer,此時的referer爲NULL
//保存文件名稱 首先是通過 --output-document 如果沒有就獲取url後綴名稱
if (opt.output_document)
{
hstat.local_file = xstrdup (opt.output_document);
got_name = true;
}
else if (!opt.content_disposition)
{
hstat.local_file = url_file_name (opt.trustservernames ? u : original_url, NULL);
/*此函數主要是如果u->file如果存在,會生成一個新的文件名file_1…如果是設置
了clobber就會覆蓋*/
got_name = true;
}  

2.3 gethttp

req = request_new ();//構造一個req頭
static struct request *
request_new (void)
{
struct request *req = xnew0 (struct request);//分配request結構
req->hcapacity = 8;//初始化http頭部數組爲8個
req->headers = xnew_array (struct request_header, req->hcapacity);//分配                                                                                              
return req;
 }

下面是請求結構

struct request {                                                                                                                                                  
const char *method;//請求方法
char *arg;		 //請求內容
/*此結構保存http header的key和value,比如content-length:xxxx
	Key爲content-length
	Value爲xxx
*/
struct request_header {
char *name, *value;
enum rp release_policy;
} *headers;
int hcount;
int hcapacity;	//此頭部容量
};

設置http方法

request_set_method(req, meth, meth_arg)
{
req->method = meth;
req->arg = arg;
}

設置http header

static void
request_set_header (struct request *req, char *name, char *value,
                     enum rp release_policy)
{
struct request_header *hdr;
int i;

if (!value)                                                                                                                                                     
{
/* A NULL value is a no-op; if freeing the name is requested,
free it now to avoid leaks.  */
if (release_policy == rel_name || release_policy == rel_both)
xfree (name);
return;
}

//首先是遍歷所有頭部,如果說找到的話,就釋放設置成新的頭
for (i = 0; i < req->hcount; i++)
{
hdr = &req->headers[i];
if (0 == strcasecmp (name, hdr->name))
{
/* Replace existing header. */
release_header (hdr);
hdr->name = name;
hdr->value = value;
hdr->release_policy = release_policy;
return;
}
}

//如果用戶設置的頭很多,超過了8個就重新分配 2的冪增長
if (req->hcount >= req->hcapacity)
 	{
req->hcapacity <<= 1;
req->headers = xrealloc (req->headers, req->hcapacity * sizeof (*hdr));
}
hdr = &req->headers[req->hcount++];
hdr->name = name;
hdr->value = value;
hdr->release_policy = release_policy;
}

後面設置頭都調用request_set_header這個函數

連接服務器:

Sock = connect_to_host (conn->host,conn->port)

如果是host爲ip地址,那麼就直接連接,如果不是,首先查找dns cache

Dns_cachehash table(如果給出的是host,就得獲得host的ip)

此hash 是通過算法把host字符串算成一個int 的key,然後再求餘算索引,然後hash處理衝突才用開放定址法。

創建key算法(key爲host,算出來的結果爲hash的key

static unsigned long
hash_string_nocase (const void *key)                                                                                                                               
{
	const char *p = key;
unsigned int h = c_tolower (*p);

if (h)
for (p += 1; *p != '\0'; p++)
h = (h << 5) - h + c_tolower (*p);

return h;
 }

查找hash key 算法是開放定址法,這裏就不說了。

Hash表的value爲structaddress_list *al

struct address_list {
int count;                    /* number of adrresses */
ip_address *addresses;        /* pointer to the string of addresses */                                                                                           

int faulty;                   /* number of addresses known not to work. */
bool connected;               /* whether we were able to connect to
                                one of the addresses in the list,
                                 at least once. */

int refcount;                 /* reference count; when it drops to
        
                           0, the entry is freed. */
};
typedef struct {
/* Address family, one of AF_INET or AF_INET6. */
int family;

/* The actual data, in the form of struct in_addr or in6_addr: */
union {
struct in_addr d4;      /* IPv4 address */
#ifdef ENABLE_IPV6
struct in6_addr d6;     /* IPv6 address */
#endif
} data;

/* Under IPv6 getaddrinfo also returns scope_id.  Since it's
Pv6-specific it strictly belongs in the above union, but we put
it here for simplicity.  */
#if defined ENABLE_IPV6 && defined HAVE_SOCKADDR_IN6_SCOPE_ID
int ipv6_scope;
#endif
} ip_address;

以下是hash表提供的接口:

cache_query(host)		//search
cache_remove(host)	//delete
cache_store(host,val)	//insert

如果在dns hash table中找不到,就調用gethostbyname api來獲取host的ip,對每一個ip port進行connect,直到連接成功爲止。連接成功host後,把req組包發送出去(request_send)

 

讀取回復頭:

Head = read_http_response_head(sock)

此時使用了select做爲事件超時和MSG_PEEK預先讀取內核socket read buffer數據,但是數據不刪除,直到找到\r\n\r\n(fd_peek),然後進行實際讀取(fd_read)

New 回覆數據包:

resp = resp_new (head);

解析數據包

讀取body部分:

hs->res = fd_read_body (sock, fp,contlen != -1 ? contlen : 0,

hs->restval, &hs->rd_size, &hs->len,&hs->dltime,flags);


Leek in beijing



發佈了83 篇原創文章 · 獲贊 8 · 訪問量 15萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章