走在架構師的大道上 Jack.Wang's home

          Java, C++, linux c, C#.net 技術,軟件架構,領域建模,IT 項目管理 Dict.CN 在線詞典, 英語學習, 在線翻譯

          BlogJava 首頁 新隨筆 聯系 聚合 管理
            195 Posts :: 3 Stories :: 728 Comments :: 0 Trackbacks

          1.      什么是REST

          REST是REpresentational State Transfer的縮寫,來源于R. Fielding的一篇博士論文:Architectural Styles and the Design of Network-based Software Architectures

          REST不是什么規范,而是一種架構,一種網絡應用的架構。可以把REST理解成一種設計模式,就像其他設計模式一樣,只不過REST這種設計模式是應用在網絡應用架構上的。

          1.1 REST的含義

          為了進一步理解什么是REST,讓我們看看REpresentational State Transfer這三個英文單詞分別表示什么意思:

          • Representational

          中文直譯:代表的,表像的。如果把WEB服務器端中所有的東西(數據)都看作是資源(Resource),那么呈現在用戶面前(客戶端)的就是資源的表像(Representation)。同一個資源可能有不同表像,例如一個人作為一個Resource,那么他的表像(Representation)可以是一張圖片(相片),也可以是一個XML描述的個人信息,等等。每一個資源都有自己的唯一標識(URI)。

          • State

          中文直譯:狀態。這個比較難理解。首先這個狀態是客戶端的狀態,而不是服務器端的狀態(在REST中,服務器端應該是無狀態的)。那么,把State和Representation聯系在一起(Representational State),可以理解成:每一個資源(Resource)在客戶端的表像(Representation)就是客戶端的一個狀態(State)。

          • Transfer

          中文直譯:轉移。當用戶通過不同的URI訪問不同的資源時,客戶端的表像(Representation)也會隨著變化,也就意味著客戶端的狀態變更(Transfer)了,連起來就是:Representational State Transfer。

           

          1.2 REST架構的特點

          • REST是一種架構,而不是一個規范。
          • REST是一種典型的Client-Server架構,但是強調瘦服務器端,服務器端只應該處理跟數據有關的操作,所有有關顯示的工作都應該放在客戶端。
          • REST架構中,服務器是無狀態的,也就是說服務器不會保存任何與客戶端的會話狀態信息。所有的狀態信息只能放在雙方溝通的Message(消息)中。
          • REST架構是冪等的,對于相同的請求,服務器返回的結果也是相同的,因此服務器端返回的結果是可以緩存的,既可以存在客戶端也可以存在代理服務器端。
          • REST架構中,所有的操作都是基于統一的方式進行的:
            • 每個Resource都有一個唯一的標識。
            • 通過Representation(客戶端)來處理Resource(服務器端)。也就是說,客戶端不能直接操作服務器端的Resource,只能通過對相應的Representation的操作,并發送相應的請求,最后由服務器端來處理Resource并返回結果。
            • 客戶端和服務器端傳送的任何一個Message(消息),都應該是自描述的。也就是說處理這個Message所需要的上下文環境都應該包含在這個Message當中。
            • 多媒體的交互系統,客戶端和服務器端傳送的內容可以是文檔,圖片,聲音等等多媒體數據,這也是一個Resource能夠對應不同的Representation(例如文檔,圖片等)的基礎。
          • 分層結構,像TCP/IP的分層結構一樣,第n層使用第n-1層提供的服務并為第n+1層提供服務。在REST中,Client-Server之間加入了Proxy層和Gateway層。在這些中間層可以加入一些業務處理以外的功能,譬如:負載均衡,安全控制等等。
          • Code-On-Demand,客戶端可以訪問服務器端的Resource,但并不知道如何處理服務器端返回的結果,這個處理過程的代碼應該是從服務器端發送過來,然后在客戶端執行,也就是說客戶端的功能是根據需要動態從服務器端獲得的。一個很簡單的例子,Applet就是從服務器端下載然后在客戶端執行的。注意,這個特性是可選的(Optional),也就是說在你的REST實現當中,可以不考慮這個特性。


          REST架構風格是全新的針對Web應用的開發風格,是當今世界最成功的互聯網超媒體分布式系統架構,它使得人們真正理解了Http協議本來面貌。隨著REST架構成為主流技術,一種全新的互聯網網絡應用開發的思維方式開始流行。

            一、REST是什么
            REST是英文Representational State Transfer的縮寫,中文翻譯為“表述性狀態轉移”,他是由Roy Thomas Fielding博士在他的論文 《Architectural Styles and the Design of Network-based Software Architectures》中提出的一個術語。REST本身只是為分布式超媒體系統設計的一種架構風格,而不是標準。
            基于Web的架構,實際上就是各種規范的集合,這些規范共同組成了Web架構。比如Http協議,比如客戶端服務器模式,這些都是規范。每當我們在原有規范的基礎上增加新的規范,就會形成新的架構。而REST正是這樣一種架構,他結合了一系列的規范,而形成了一種新的基于Web的架構風格。

            傳統的Web應用大都是B/S架構,它包括了如下一些規范 。
            1.客戶-服務器:這種規范的提出,改善了用戶接口跨多個平臺的可移植性,并且通過簡化服務器組件,改善了系統的可伸縮性。最為關鍵的是通過分離用戶接口和數據存儲這兩個關注點,使得不同用戶終端享受相同數據成為了可能。
            2.無狀態性:無狀態性是在客戶-服務器約束的基礎上添加的又一層規范。他要求通信必須在本質上是無狀態的,即從客戶到服務器的每個request都必須包含理解該request所必須的所有信息。這個規范改善了系統的可見性(無狀態性使得客戶端和服務器端不必保存對方的詳細信息,服務器只需要處理當前request,而不必了解所有的request歷史),可靠性(無狀態性減少了服務器從局部錯誤中恢復的任務量),可伸縮性(無狀態性使得服務器端可以很容易的釋放資源,因為服務器端不必在多個request中保存狀態)。同時,這種規范的缺點也是顯而易見得,由于不能將狀態數據保存在服務器上的共享上下文中,因此增加了在一系列request中發送重復數據的開銷,嚴重的降低了效率。
            3.緩存:為了改善無狀態性帶來的網絡的低效性,我們填加了緩存約束。緩存約束允許隱式或顯式地標記一個response中的數據,這樣就賦予了客戶端緩存response數據的功能,這樣就可以為以后的request共用緩存的數據,部分或全部的消除一部分交互,增加了網絡的效率。但是用于客戶端緩存了信息,也就同時增加了客戶端與服務器數據不一致的可能,從而降低了可靠性。

            B/S架構的優點是其部署非常方便,但在用戶體驗方面卻不是很理想。為了改善這種情況,我們引入了REST。
            REST在原有的架構上增加了三個新規范:統一接口、分層系統和按需代碼。
            1.統一接口:REST架構風格的核心特征就是強調組件之間有一個統一的接口,這表現在REST世界里,網絡上所有的事物都被抽象為資源,而REST就是通過通用的鏈接器接口對資源進行操作。這樣設計的好處是保證系統提供的服務都是解耦的,極大的簡化了系統,從而改善了系統的交互性和可重用性。并且REST針對Web的常見情況做了優化,使得REST接口被設計為可以高效的轉移大粒度的超媒體數據,這也就導致了REST接口對其它的架構并不是最優的。
            2.分層系統:分層系統規則的加入提高了各種層次之間的獨立性,為整個系統的復雜性設置了邊界,通過封裝遺留的服務,使新的服務器免受遺留客戶端的影響,這也就提高了系統的可伸縮性。
            3.按需代碼:REST允許對客戶端功能進行擴展。比如,通過下載并執行applet或腳本形式的代碼,來擴展客戶端功能。但這在改善系統可擴展性的同時,也降低了可見性。所以它只是REST的一個可選的約束。
               
            二、REST的設計準則
            REST架構是針對Web應用而設計的,其目的是為了降低開發的復雜性,提高系統的可伸縮性。REST提出了如下設計準則:
          1.網絡上的所有事物都被抽象為資源(resource);
          2.每個資源對應一個唯一的資源標識符(resource identifier); 
          3.通過通用的連接器接口(generic connector interface)對資源進行操作;
          4.對資源的各種操作不會改變資源標識符;
          5.所有的操作都是無狀態的(stateless)。
            REST中的資源所指的不是數據,而是數據和表現形式的組合,比如“最新訪問的10位會員”和“最活躍的10為會員”在數據上可能有重疊或者完全相同,而由于他們的表現形式不同,所以被歸為不同的資源,這也就是為什么REST的全名是Representational State Transfer的原因。資源標識符就是URI(Uniform Resource Identifier),不管是圖片,Word還是視頻文件,甚至只是一種虛擬的服務,也不管你是xml格式,txt文件格式還是其它文件格式,全部通過URI對資源進行唯一的標識。
            REST是基于Http協議的,任何對資源的操作行為都是通過Http協議來實現。以往的Web開發大多數用的都是Http協議中的GET和POST方法,對其他方法很少使用,這實際上是因為對Http協議認識片面的理解造成的。Http不僅僅是一個簡單的運載數據的協議,而是一個具有豐富內涵的網絡軟件的協議。他不僅僅能對互聯網資源進行唯一定位,而且還能告訴我們如何對該資源進行操作。Http把對一個資源的操作限制在4個方法以內:GET,POST,PUT和DELETE,這正是對資源CRUD操作的實現。由于資源和URI是一一對應的,執行這些操作的時候URI是沒有變化的,這和以往的Web開發有很大的區別。正由于這一點,極大的簡化了Web開發,也使得URI可以被設計成更為直觀的反映資源的結構,這種URI的設計被稱作RESTful的URI。這位開發人員引入了一種新的思維方式:通過URL來設計系統結構。當然了,這種設計方式對一些特定情況也是不適用的,也就是說不是所有的URI都可以RESTful的。
            REST之所以可以提高系統的可伸縮性,就是因為它要求所有的操作都是無狀態的。由于沒有了上下文(Context)的約束,做分布式和集群的時候就更為簡單,也可以讓系統更為有效的利用緩沖池(Pool)。并且由于服務器端不需要記錄客戶端的一系列訪問,也減少了服務器端的性能。

              三、使用REST架構
            對于開發人員來說,關心的是如何使用REST架構,這里我們來簡單談談這個問題。REST不僅僅是一種嶄新的架構,它帶來的更是一種全新的Web開發過程中的思維方式:通過URL來設計系統結構。在REST中,所有的URL都對應著資源,只要URL的設計是良好的,那么其呈現的系統結構也就是良好的。這點和TDD(Test Driven Development)很相似,他是通過測試用例來設計系統的接口,每一個測試用例都表示一系列用戶的需求。開發人員不需要一開始就編寫功能,而只需要把需要實現的功能通過測試用例的形式表現出來即可。這個和REST中通過URL設計系統結構的方式類似,我們只需要根據需求設計出合理地URL,這些URL不一定非要鏈接到指定的頁面或者完成一些行為,只要它們能夠直觀的表現出系統的用戶接口。根據這些URL,我們就可以方便的設計系統結構。從REST架構的概念上來看,所有能夠被抽象成資源的東西都可以被指定為一個URL,而開發人員所需要做的工作就是如何能把用戶需求抽象為資源,以及如何抽象的精確。因為對資源抽象的越為精確,對REST的應用來說就越好。這個和傳統MVC開發模式中基于Action的思想差別就非常大。設計良好的URL,不但對于開發人員來說可以更明確的認識系統結構,對使用者來說也方便記憶和識別資源,因為URL足夠簡單和有意義。按照以往的設計模式,很多URL后面都是一堆參數,對于使用者來說也是很不方便的。
            既然REST這么好用,那么是不是所有的Web應用都能采取此種架構呢?答案是否定的。我們知道,直到現在為止,MVC(Model-View-Controller)模式依然是Web開發最普遍的模式,絕大多數的公司和開發人員都采取此種架構來開發Web應用,并且其思維方式也停留于此。MVC模式由數據,視圖和控制器構成,通過事件(Event)觸發Controller來改變Model和View。加上Webwork,Struts等開源框架的加入,MVC開發模式已經相當成熟,其思想根本就是基于Action來驅動。從開發人員角度上來說,貿然接受一個新的架構會帶來風險,其中的不確定因素太多。并且REST新的思維方式是把所有用戶需求抽象為資源,這在實際開發中是比較難做到的,因為并不是所有的用戶需求都能被抽象為資源,這樣也就是說不是整個系統的結構都能通過REST的來表現。所以在開發中,我們需要根據以上2點來在REST和MVC中做出選擇。我們認為比較好的辦法是混用REST和MVC,因為這適合絕大多數的Web應用開發,開發人員只需要對比較容易能夠抽象為資源的用戶需求采取REST的開發模式,而對其它需求采取MVC開發即可。這里需要提到的就是ROR(Ruby on Rails)框架,這是一個基于Ruby語言的越來越流行的Web開發框架,它極大的提高了Web開發的速度。更為重要的是,ROR(從1.2版本起)框架是第一個引入REST做為核心思想的Web開發框架,它提供了對REST最好的支持,也是當今最成功的應用REST的Web開發框架。實際上,ROR的REST實現就是REST和MVC混用,開發人員采用ROR框架,可以更快更好的構建Web應用。
            對開發人員來說,REST不僅僅在Web開發上貢獻了自己的力量,同時也讓我們學到了如何把軟件工程原則系統地應用于對一個真實軟件的設計和評估上。


          理解REST軟件架構

           一種思維方式影響了
          軟件行業的發展。REST軟件架構是當今世界上最成功的互聯網的超媒體分布式系統。它讓人們真正理解我們的網絡協議HTTP本來面貌。它正在成為網絡服務的主流技術,同時也正在改變互聯網的網絡軟件開發的全新思維方式。AJAX技術和Rails框架把REST軟件架構思想真正地在實際中很好表現出來。今天微軟也已經應用REST并且提出把我們現有的網絡變成為一個語義網,這種網絡將會使得搜索更加智能化。

          REST與HTTP協議

          REST軟件架構是由Roy Thomas Fielding博士在2000年首次提出的。他為我們描繪了開發基于互聯網的網絡軟件的藍圖。REST軟件架構是一個抽象的概念,是一種為了實現這一互聯網的超媒體分布式系統的行動指南。利用任何的技術都可以實現這種理念。而實現這一軟件架構最著名的就是HTTP協議。通常我們把REST也寫作為REST/HTTP,在實際中往往把REST理解為基于HTTP的REST軟件架構,或者更進一步把REST和HTTP看作為等同的概念。

          今天,HTTP是互聯網上應用最廣泛的計算機協議。HTTP不是一個簡單的運載數據的協議,而是一個具有豐富內涵的網絡軟件的協議。它不僅僅能夠對于互聯網資源進行唯一定位,而且還能告訴我們對于該資源進行怎樣運作。這也是REST軟件架構當中最重要的兩個理念。而REST軟件架構理念是真正理解HTTP協議而形成的。有了REST軟件架構理念出現,才使得軟件業避免了對HTTP協議的片面理解。只有正確的理論指導,才能避免在軟件開發的實際工作過程中少走彎路。

          REST與URI(資源定位)

          REST軟件架構之所以是一個超媒體系統,是因為它可以把網絡上所有資源進行唯一的定位,不管你的文件是圖片、文件Word還是視頻文件,也不管你的文件是txt文件格式、xml文件格式還是其它文本文件格式。它利用支持HTTP的TCP/IP協議來確定互聯網上的資源。

          REST與CRUD原則

          REST軟件架構遵循了CRUD原則,該原則告訴我們對于資源(包括網絡資源)只需要四種行為:創建(Create)、獲取(Read)、更新(Update)和銷毀(DELETE)就可以完成對其操作和處理了。其實世界萬物都是遵循這一規律:生、變、見、滅。所以計算機世界也不例外。這個原則是源自于我們對于數據庫表的數據操作:insert(生)、select(見)、update(變)和delete(滅),所以有時候CRUD也寫作為RUDI,其中的I就是insert。這四個操作是一種原子操作,即一種無法再分的操作,通過它們可以構造復雜的操作過程,正如數學上四則運算是數字的最基本的運算一樣。

          REST與網絡服務

          盡管在Java語言世界中網絡服務目前是以SOAP技術為主,但是REST將是是網絡服務的另一選擇,并且是真正意義上的網絡服務。基于REST思想的網絡服務不久的將來也會成為是網絡服務的主流技術。REST不僅僅把HTTP作為自己的數據運輸協議,而且也作為直接進行數據處理的工具。而當前的網絡服務技術都需要使用其它手段來完成數據處理工作,它們完全獨立于HTTP協議來進行的,這樣增加了大量的復雜軟件架構設計工作。REST的思想充分利用了現有的HTTP技術的網絡能力。在德國電視臺上曾經出現過一個這樣的五十萬歐元智力題:如何實現網絡服務才能充分利用現有的HTTP協議?該問題給出了四個答案:去問微軟;WSDL2.0/SOAP1.2;WS-Transfer;根本沒有。這個問題告訴我們HTTP并不是一個簡單的數據傳來傳去的協議,而是一個聰明的會表現自己的協議,這也許是REST = Representational State Transfer的真正含義。

          實際上目前很多大公司已經采用了REST技術作為網絡服務,如Google、Amazon等。在Java語言中重要的兩個以SOAP技術開始的網絡服務框架XFire和Axis也把REST作為自己的另一種選擇。它們的新的項目分別是Apache CXFAxis2。Java語言也制定關于REST網絡服務規范:JAX-RS: Java API for RESTful Web Services (JSR 311)。相信還會出現更多與REST相關的激動人心的信息。

          REST與AJAX技術

          盡管AJAX技術的出現才不到兩年時間,但是AJAX技術遵循了REST的一些重要原則。AJAX技術充分利用了HTTP來獲取網絡資源并且實現了HTTP沒有的對于異步數據進行傳輸的功能。AJAX技術還使得軟件更好地實現分布性功能,在一個企業內只要一個人下載了AJAX引擎,其它企業內部的人員,就可以共享該資源了。AJAX技術遵守REST準則的應用程序中簡單和可伸縮的架構,凡是采用AJAX技術的頁面簡潔而又豐富,一個頁面表現了豐富多彩的形態。

          AJAX技術還使用了一種不同于XML格式的JSON文件格式,這個意義在哪里呢?在REST軟件架構下我們不能對于XML文件進行序列化處理,這樣程序員必須要使用自己的XML綁定框架。而以序列化的JavaScript對象為基礎的JSON已經獲得了廣泛認可,它被認為能以遠比XML更好的方式來序列化和傳輸簡單數據結構,而且它更簡潔。這對REST是一個極大貢獻和補充。

          當前的網絡應用軟件還違背了REST的“無狀態服務器”約束。REST服務器只知道自己的狀態。REST不關心客戶端的狀態,客戶端的狀態自己來管理,這是AJAX技術的應用之地。通過AJAX技術,可以發揮有狀態網絡客戶機的優勢。而REST的服務器關心的是從所有網絡客戶端發送到服務器操作的順序。這樣使得互聯網這樣一個巨大的網絡得到有序的管理。

          REST與Rails框架

          Ruby on Rails框架(簡稱Rails或者Rails框架)是一個基于Ruby語言的越來越流行的網絡應用軟件開發框架。它提供了關于REST最好的支持,也是當今應用REST最成功的一個軟件開發框架。Rails框架(從版本1.2.x起)成為了第一個引入REST作為核心思想的主流網絡軟件開發框架。在Rails框架的充分利用了REST軟件架構之后,人們更加堅信REST的重要性和必要性。Rails利用REST軟件架構思想對網絡服務也提供了一流的支持。從最直觀的角度看待REST,它是網絡服務最理想的手段,但是Rails框架把REST帶到了網絡應用軟件開發框架。這是一次飛躍,讓REST的思想從網絡服務的應用提升到了網絡應用軟件開發。利用REST思想的simply_restful插件已經成為了Rails框架的核心內容。

          REST安全性

          我們把現有基于SOAP的網絡服務和基于REST/HTTP網絡服務作個比喻,前者是一種傳統的寄信方式,而后者是現代網絡的電子郵件方式。要是是寄信和電子郵件都有病毒存在的話,傳統的寄信被送到對方就很危險,而電子郵件是開發的,電子郵件供應商比如Google為我們檢查了電子郵件是否有病毒。這里并不是說明SOAP網絡服務消息包含義病毒,而是說明HTTP是無法處理SOAP信息包究竟好不好,需要額外的軟件工具解決這一問題,包括防火墻也用不上和管不了。

          REST/HTTP網絡服務的信息包可以被防火墻理解和控制。你可以按照操作和鏈接進行過濾信息包,如你可以規定從外部來的只能讀取(GET操作)自己服務器的資源。這樣對于系統管理員而言使得軟件管理更為簡單。REST的安全性還可以利用傳輸安全協議SSL/TLS、基本和摘要式認證(Basic und Digest Authentication)。除了這些REST自身的安全性功能外,還可以利用像基于信息的Web Services Security(JSR 155)作為REST不錯的補充。






          本博客為學習交流用,凡未注明引用的均為本人作品,轉載請注明出處,如有版權問題請及時通知。由于博客時間倉促,錯誤之處敬請諒解,有任何意見可給我留言,愿共同學習進步。
          posted on 2008-05-15 18:47 Jack.Wang 閱讀(9731) 評論(3)  編輯  收藏 所屬分類: 開發技術

          Feedback

          # re: 淺談REST 2008-05-16 11:43 hmt
          > Http把對一個資源的操作限制在4個方法以內:GET,POST,PUT和DELETE,這正是對資源CRUD操作的實現。

          把HTTP當成CRUD來使用,將無法發揮REST中"Hypermedia as application state engine"所帶來的好處。這是Roy Fielding一再強調的:

          http://tech.groups.yahoo.com/group/rest-discuss/message/10740  回復  更多評論
            

          # re: 淺談REST 2008-05-16 12:37 Jack.Wang
          There seems to be a common thread with most posts here. People
          have been busy modeling everything as a resource and now they
          want to know how to do everything in a PUT or DELETE instead of
          any of the other HTTP methods. That is wrong. That is thinking
          HTTP is just a "Save as..." dialog.

          REST is not limited to GET, PUT, and DELETE. Anyone who says so
          is just making things up as they go along. REST is limited to the
          client being told what to do next by the current state of where
          they are now, aside from the entry point(s) we call a bookmark.
          That is feasible because the set of methods is uniform, not because
          it is limited to CRUD. POST is an equal party in the REST interface,
          particularly when actions are being applied to the resources that
          are a composite of multiple source resources. So is PATCH.

          Doing RESTful actions on multiple resources is no different from
          selecting multiple tunes in iTunes and using the info dialog
          to set certain properties across all selected tunes. It is a UI issue.
          The UI builds a set of actions to perform and then performs them
          in whatever way is most efficiently provided by the set of resources
          being operated upon. If those resources (or, rather, the index to
          those resources) say they can be operated upon as a group by POST
          of a selection form, then so be it -- that is perfectly RESTful
          even without the benefit of per-resource cache invalidation.
          Likewise for PATCH or PUT to a meta-resource, or PROPPATCH to a
          WebDAV resource, it is RESTful if there is some engine described
          by a representation provided by the origin server that instructs
          the client on what to do next.

          The goals are to remove coupling and maximize the number of
          reusable resources.

          ....Roy  回復  更多評論
            

          # re: 淺談REST 2008-05-16 17:28 Jack.Wang
          我看過一些文檔,感覺 Web Service 還不太成熟,只能作為異構系統之間相互集成時的膠水。以前 robbin 寫過介紹 Web Service 的文章對 Web Service 的定位也基本上是這樣。Web Service 并不適合核心的業務領域,其組件模型相比 CORBA、EJB 來說是非常不成熟的。核心的應用領域還是要靠 CORBA、EJB 來建造。
          基于 HTTP 只是一個 Marketing Hype,不要上當。CORBA 如果配置在 80 端口同樣可以穿越防火墻。HTTP 本身就是一個低效的傳輸協議,基于文本的 XML 更加低效,在高強度的核心應用領域,不要跟我說性能是無關緊要的。另外還有安全性等 CORBA 已經解決得很好的問題 Web Service 好象還沒有完全解決(是不是老黃歷了?)
          簡單的技術就應該用在簡單的場合,一定要在復雜的核心應用領域采用簡單的技術是會出問題的。CORBA 確實很復雜,但是要搞清楚究竟是因為要解決的問題本身很復雜所以技術也很復雜還是因為一幫技術狂人閑的無聊故意搞得這么復雜以便大家對他們頂禮膜拜。
          我的看法是 Web Service 與 CORBA 的定位不同,所以不存在誰滅掉誰的問題。
          一點淺見,歡迎討論。  回復  更多評論
            

          主站蜘蛛池模板: 精河县| 永胜县| 康定县| 铜山县| 沁源县| 新河县| 白朗县| 太仆寺旗| 三穗县| 杭州市| 揭东县| 平武县| 北海市| 大渡口区| 南木林县| 庆城县| 册亨县| 宣威市| 蓬溪县| 安岳县| 高邮市| 乐亭县| 德钦县| 镇平县| 衡水市| 名山县| 临安市| 紫金县| 建昌县| 巴中市| 康平县| 普定县| 西青区| 伊川县| 清水县| 平遥县| 黑龙江省| 武城县| 益阳市| 永平县| 眉山市|